diff --git a/.eslintrc b/.eslintrc
index 73cd7ecf66d7261830d430f6dc626e6a2fb835a2..c72a5e0335bda10b9b1576c23a7171b64c5fde5b 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -11,6 +11,7 @@
     "gon": false,
     "localStorage": false
   },
+  "parser": "babel-eslint",
   "plugins": [
     "filenames",
     "import",
diff --git a/.gitignore b/.gitignore
index 89da29fd7904f484d7b65c7d9e1fef05b9a0994e..0d6194dd1e5346077955d3ffed3f7f174b21cccb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,6 +21,7 @@ eslint-report.html
 /.yarn-cache
 /.byebug_history
 /Vagrantfile
+/app/assets/javascripts/locale/**/app.js
 /backups/*
 /config/aws.yml
 /config/database.yml
@@ -59,3 +60,4 @@ eslint-report.html
 /.gitlab_workhorse_secret
 /webpack-report/
 /locale/**/LC_MESSAGES
+/.rspec
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index f0c266485b675d53926fcb16ea2a5c3138f53d3e..a3ce1de50c2739aa9d6bb692a2dd3ac5b42c520a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -63,7 +63,7 @@ stages:
 .only-master-and-ee-or-mysql: &only-master-and-ee-or-mysql
   only:
     - /mysql/
-    - /-stable$/
+    - /-stable/
     - master@gitlab-org/gitlab-ce
     - master@gitlab/gitlabhq
     - tags@gitlab-org/gitlab-ce
@@ -193,6 +193,7 @@ setup-test-env:
   script:
     - node --version
     - yarn install --pure-lockfile --cache-folder .yarn-cache
+    - bundle exec rake gettext:po_to_json
     - bundle exec rake gitlab:assets:compile
     - bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init'
   artifacts:
@@ -433,6 +434,7 @@ gitlab:assets:compile:
     NO_COMPRESSION: "true"
   script:
     - yarn install --pure-lockfile --production --cache-folder .yarn-cache
+    - bundle exec rake gettext:po_to_json
     - bundle exec rake gitlab:assets:compile
   artifacts:
     name: webpack-report
@@ -450,6 +452,7 @@ karma:
     BABEL_ENV: "coverage"
     CHROME_LOG_FILE: "chrome_debug.log"
   script:
+    - bundle exec rake gettext:po_to_json
     - bundle exec rake karma
   coverage: '/^Statements *: (\d+\.\d+%)/'
   artifacts:
@@ -461,6 +464,7 @@ karma:
     - coverage-javascript/
 
 codeclimate:
+  <<: *except-docs
   before_script: []
   image: docker:latest
   stage: test
@@ -470,8 +474,8 @@ codeclimate:
   services:
     - docker:dind
   script:
-    - docker pull codeclimate/codeclimate
-    - docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate analyze -f json > codeclimate.json
+    - docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate analyze -f json > raw_codeclimate.json
+    - cat raw_codeclimate.json | docker run -i stedolan/jq -c 'map({check_name,fingerprint,location})' > codeclimate.json
   artifacts:
     paths: [codeclimate.json]
 
@@ -546,3 +550,9 @@ cache gems:
   only:
     - master@gitlab-org/gitlab-ce
     - master@gitlab-org/gitlab-ee
+
+gitlab_git_test:
+  variables:
+    SETUP_DB: "false"
+  script:
+    - spec/support/prepare-gitlab-git-test-for-commit --check-for-changes
diff --git a/.gitlab/issue_templates/Bug.md b/.gitlab/issue_templates/Bug.md
index 9d53a48409a8f7e19ded72c49bbb87359760eda4..aec734870d65c850d3f33f364d706f4ec6b6ba61 100644
--- a/.gitlab/issue_templates/Bug.md
+++ b/.gitlab/issue_templates/Bug.md
@@ -1,11 +1,18 @@
 Please read this!
 
 Before opening a new issue, make sure to search for keywords in the issues
-filtered by the "regression" or "bug" label:
+filtered by the "regression" or "bug" label.
+
+For the Community Edition issue tracker:
 
 - https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=regression
 - https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=bug
 
+For the Enterprise Edition issue tracker:
+
+- https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name%5B%5D=regression
+- https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name%5B%5D=bug
+
 and verify the issue you're about to submit isn't a duplicate.
 
 Please remove this notice if you're confident your issue isn't a duplicate.
diff --git a/.gitlab/issue_templates/Feature Proposal.md b/.gitlab/issue_templates/Feature Proposal.md
index d96c9ad59e0b5e0c2d148be157f79c8790243dc7..1278061a410130f90c4425f38f95a7eae07f68ce 100644
--- a/.gitlab/issue_templates/Feature Proposal.md	
+++ b/.gitlab/issue_templates/Feature Proposal.md	
@@ -3,8 +3,14 @@ Please read this!
 Before opening a new issue, make sure to search for keywords in the issues
 filtered by the "feature proposal" label:
 
+For the Community Edition issue tracker:
+
 - https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=feature+proposal
 
+For the Enterprise Edition issue tracker:
+
+- https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name%5B%5D=feature+proposal
+
 and verify the issue you're about to submit isn't a duplicate.
 
 Please remove this notice if you're confident your issue isn't a duplicate.
@@ -21,12 +27,24 @@ Please remove this notice if you're confident your issue isn't a duplicate.
 
 ### Documentation blurb
 
-(Write the start of the documentation of this feature here, include:
+#### Overview
+
+What is it?
+Why should someone use this feature?
+What is the underlying (business) problem?
+How do you use this feature?
+
+#### Use cases
+
+Who is this for? Provide one or more use cases.
+
+### Feature checklist
 
-1. Why should someone use it; what's the underlying problem.
-2. What is the solution.
-3. How does someone use this
+Make sure these are completed before closing the issue,
+with a link to the relevant commit.
 
-During implementation, this can then be copied and used as a starter for the documentation.)
+- [ ] [Feature assurance](https://about.gitlab.com/handbook/product/#feature-assurance)
+- [ ] Documentation
+- [ ] Added to [features.yml](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/features.yml)
 
-/label ~"feature proposal"
+/label ~"feature proposal"
\ No newline at end of file
diff --git a/.rspec b/.rspec
deleted file mode 100644
index 35f4d7441e0733559289aa9a6d8621392cee485b..0000000000000000000000000000000000000000
--- a/.rspec
+++ /dev/null
@@ -1,2 +0,0 @@
---color
---format Fuubar
diff --git a/.rubocop.yml b/.rubocop.yml
index 4537e710dd488424ec18479370520b92736fe131..9785e7626f945b6fdbaee1741f3a4f014281402f 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -164,6 +164,11 @@ Style/DefWithParentheses:
 Style/Documentation:
   Enabled: false
 
+# Multi-line method chaining should be done with leading dots.
+Style/DotPosition:
+  Enabled: true
+  EnforcedStyle: leading
+
 # This cop checks for uses of double negation (!!) to convert something
 # to a boolean value. As this is both cryptic and usually redundant, it
 # should be avoided.
@@ -960,6 +965,10 @@ RSpec/AnyInstance:
 RSpec/BeEql:
   Enabled: true
 
+# We don't enforce this as we use this technique in a few places.
+RSpec/BeforeAfterAll:
+  Enabled: false
+
 # Check that the first argument to the top level describe is the tested class or
 # module.
 RSpec/DescribeClass:
@@ -1019,6 +1028,12 @@ RSpec/FilePath:
 RSpec/Focus:
   Enabled: true
 
+# Configuration parameters: EnforcedStyle, SupportedStyles.
+# SupportedStyles: is_expected, should
+RSpec/ImplicitExpect:
+  Enabled: true
+  EnforcedStyle: is_expected
+
 # Checks for the usage of instance variables.
 RSpec/InstanceVariable:
   Enabled: false
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index e2d9c37479d339a67ef0b4936500d83d2da8654e..2ec558e274f64af7abd9d2cef9bb264374288307 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -6,10 +6,6 @@
 # Note that changes in the inspected code, or installation of new
 # versions of RuboCop, may require this file to be generated again.
 
-# Offense count: 54
-RSpec/BeforeAfterAll:
-  Enabled: false
-
 # Offense count: 233
 RSpec/EmptyLineAfterFinalLet:
   Enabled: false
@@ -24,12 +20,6 @@ RSpec/EmptyLineAfterSubject:
 RSpec/HookArgument:
   Enabled: false
 
-# Offense count: 12
-# Configuration parameters: EnforcedStyle, SupportedStyles.
-# SupportedStyles: is_expected, should
-RSpec/ImplicitExpect:
-  Enabled: false
-
 # Offense count: 11
 # Configuration parameters: EnforcedStyle, SupportedStyles.
 # SupportedStyles: it_behaves_like, it_should_behave_like
@@ -88,13 +78,6 @@ Security/YAMLLoad:
 Style/BarePercentLiterals:
   Enabled: false
 
-# Offense count: 1403
-# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, SupportedStyles.
-# SupportedStyles: leading, trailing
-Style/DotPosition:
-  Enabled: false
-
 # Offense count: 5
 # Cop supports --auto-correct.
 Style/EachWithObject:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f43858a00a5104f47f34a2617beb20aaa1f87405..4d2adb47a80fc3f6608746c245a164ef31ee3575 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,259 @@
 documentation](doc/development/changelog.md) for instructions on adding your own
 entry.
 
+## 9.3.5 (2017-07-05)
+
+- Remove "Remove from board" button from backlog and closed list. !12430
+- Do not delete protected branches when deleting all merged branches. !12624
+- Set default for Remove source branch to false.
+- Prevent accidental deletion of protected MR source branch by repeating checks before actual deletion.
+- Expires full_path cache after a repository is renamed/transferred.
+
+## 9.3.4 (2017-07-03)
+
+- No changes.
+
+## 9.3.3 (2017-06-30)
+
+- Fix head pipeline stored in merge request for external pipelines. !12478
+- Bring back branches badge to main project page. !12548
+- Fix diff of requirements.txt file by not matching newlines as part of package names.
+- Perform housekeeping only when an import of a fresh project is completed.
+- Fixed issue boards closed list not showing all closed issues.
+- Fixed multi-line markdown tooltip buttons in issue edit form.
+
+## 9.3.2 (2017-06-27)
+
+- API: Fix optional arugments for POST :id/variables. !12474
+- Bump premailer-rails gem to 1.9.7 and its dependencies to prevent network retrieval of assets.
+
+## 9.3.1 (2017-06-26)
+
+- Fix reversed breadcrumb order for nested groups. !12322
+- Fix 500 when failing to create private group. !12394
+- Fix linking to line number on side-by-side diff creating empty discussion box.
+- Don't match tilde and exclamation mark as part of requirements.txt package name.
+- Perform project housekeeping after importing projects.
+- Fixed ctrl+enter not submit issue edit form.
+
+## 9.3.0 (2017-06-22)
+
+- Refactored gitlab:app:check into SystemCheck liberary and improve some checks. !9173
+- Add an ability to cancel attaching file and redesign attaching files UI. !9431 (blackst0ne)
+- Add Aliyun OSS as the backup storage provider. !9721 (Yuanfei Zhu)
+- Add suport for find_local_branches GRPC from Gitaly. !10059
+- Allow manual bypass of auto_sign_in_with_provider with a new param. !10187 (Maxime Besson)
+- Redirect to user's keys index instead of user's index after a key is deleted in the admin. !10227 (Cyril Jouve)
+- Changed Blame to Annotate in the UI to promote blameless culture. !10378 (Ilya Vassilevsky)
+- Implement ability to update deploy keys. !10383 (Alexander Randa)
+- Allow numeric values in gitlab-ci.yml. !10607 (blackst0ne)
+- Add a feature test for Unicode trace. !10736 (dosuken123)
+- Notes: Warning message should go away once resolved. !10823 (Jacopo Beschi @jacopo-beschi)
+- Project authorizations are calculated much faster when using PostgreSQL, and nested groups support for MySQL has been removed
+. !10885
+- Fix long urls in the title of commit. !10938 (Alexander Randa)
+- Update gem sidekiq-cron from 0.4.4 to 0.6.0 and rufus-scheduler from 3.1.10 to 3.4.0. !10976 (dosuken123)
+- Use relative paths for group/project/user avatars. !11001 (blackst0ne)
+- Enable cancelling non-HEAD pending pipelines by default for all projects. !11023
+- Implement web hook logging. !11027 (Alexander Randa)
+- Add indices for auto_canceled_by_id for ci_pipelines and ci_builds on PostgreSQL. !11034
+- Add post-deploy migration to clean up projects in `pending_delete` state. !11044
+- Limit User's trackable attributes, like `current_sign_in_at`, to update at most once/hour. !11053
+- Disallow multiple selections for Milestone dropdown. !11084
+- Link to commit author user page from pipelines. !11100
+- Fix the last coverage in trace log should be extracted. !11128 (dosuken123)
+- Remove redirect for old issue url containing id instead of iid. !11135 (blackst0ne)
+- Backported new SystemHook event: `repository_update`. !11140
+- Keep input data after creating a tag that already exists. !11155
+- Fix support for external CI services. !11176
+- Translate backend for Project & Repository pages. !11183
+- Fix LaTeX formatting for AsciiDoc wiki. !11212
+- Add foreign key for pipeline schedule owner. !11233
+- Print Go version in rake gitlab:env:info. !11241
+- Include the blob content when printing a blob page. !11247
+- Sync email address from specified omniauth provider. !11268 (Robin Bobbitt)
+- Disable reference prefixes in notes for Snippets. !11278
+- Rename build_events to job_events. !11287
+- Add API support for pipeline schedule. !11307 (dosuken123)
+- Use route.cache_key for project list cache key. !11325
+- Make environment table realtime. !11333
+- Cache npm modules between pipelines with yarn to speed up setup-test-env. !11343
+- Allow GitLab instance to start when InfluxDB hostname cannot be resolved. !11356
+- Add ConvDev Index page to admin area. !11377
+- Fix Git-over-HTTP error statuses and improve error messages. !11398
+- Renamed users 'Audit Log'' to 'Authentication Log'. !11400
+- Style people in issuable search bar. !11402
+- Change /builds in the URL to /-/jobs. Backward URLs were also added. !11407
+- Update password field label while editing service settings. !11431
+- Add an optional performance bar to view performance metrics for the current page. !11439
+- Update task_list to version 2.0.0. !11525 (Jared Deckard <jared.deckard@gmail.com>)
+- Avoid resource intensive login checks if password is not provided. !11537 (Horatiu Eugen Vlad)
+- Allow numeric pages domain. !11550
+- Exclude manual actions when checking if pipeline can be canceled. !11562
+- Add server uptime to System Info page in admin dashboard. !11590 (Justin Boltz)
+- Simplify testing and saving service integrations. !11599
+- Fixed handling of the `can_push` attribute in the v3 deploy_keys api. !11607 (Richard Clamp)
+- Improve user experience around slash commands in instant comments. !11612
+- Show current user immediately in issuable filters. !11630
+- Add extra context-sensitive functionality for the top right menu button. !11632
+- Reorder Issue action buttons in order of usability. !11642
+- Expose atom links with an RSS token instead of using the private token. !11647 (Alexis Reigel)
+- Respect merge, instead of push, permissions for protected actions. !11648
+- Job details page update real time. !11651
+- Improve performance of ProjectFinder used in /projects API endpoint. !11666
+- Remove redundant data-turbolink attributes from links. !11672 (blackst0ne)
+- Minimum postgresql version is now 9.2. !11677
+- Add protected variables which would only be passed to protected branches or protected tags. !11688
+- Introduce optimistic locking support via optional parameter last_commit_sha on File Update API. !11694 (electroma)
+- Add $CI_ENVIRONMENT_URL to predefined variables for pipelines. !11695
+- Simplify project repository settings page. !11698
+- Fix pipeline_schedules pages throwing error 500. !11706 (dosuken123)
+- Add performance deltas between app deployments on Merge Request widget. !11730
+- Add feature toggles and API endpoints for admins. !11747
+- Replace 'starred_projects.feature' spinach test with an rspec analog. !11752 (blackst0ne)
+- Introduce an Events API. !11755
+- Display Shared Runner status in Admin Dashboard. !11783 (Ivan Chernov)
+- Persist pipeline stages in the database. !11790
+- Revert the feature that would include the current user's username in the HTTP clone URL. !11792
+- Enable Gitaly by default in installations from source. !11796
+- Use zopfli compression for frontend assets. !11798
+- Add tag_list param to project api. !11799 (Ivan Chernov)
+- Add changelog for improved Registry description. !11816
+- Automatically adjust project settings to match changes in project visibility. !11831
+- Add slugify project path to CI enviroment variables. !11838 (Ivan Chernov)
+- Add all pipeline sources as special keywords to 'only' and 'except'. !11844 (Filip Krakowski)
+- Allow pulling of container images using personal access tokens. !11845
+- Expose import_status in Projects API. !11851 (Robin Bobbitt)
+- Allow admins to delete users from the admin users page. !11852
+- Allow users to be hard-deleted from the API. !11853
+- Fix hard-deleting users when they have authored issues. !11855
+- Fix missing optional path parameter in "Create project for user" API. !11868
+- Allow users to be hard-deleted from the admin panel. !11874
+- Add a Rake task to aid in rotating otp_key_base. !11881
+- Fix submodule link to then project under subgroup. !11906
+- Fix binary encoding error on MR diffs. !11929
+- Limit non-administrators to adding 100 members at a time to groups and projects. !11940
+- add bulgarian translation of cycle analytics page to I18N. !11958 (Lyubomir Vasilev)
+- Make backup task to continue on corrupt repositories. !11962
+- Fix incorrect ETag cache key when relative instance URL is used. !11964
+- Reinstate is_admin flag in users api when authenticated user is an admin. !12211 (rickettm)
+- Fix edit button for deploy keys available from other projects. !12301 (Alexander Randa)
+- Fix passing CI_ENVIRONMENT_NAME and CI_ENVIRONMENT_SLUG for CI_ENVIRONMENT_URL. !12344
+- Disable environment list refresh due to bug https://gitlab.com/gitlab-org/gitlab-ee/issues/2677. !12347
+- Standardize timeline note margins across different viewport sizes. !12364
+- Fix Ordered Task List Items. !31483 (Jared Deckard <jared.deckard@gmail.com>)
+- Upgrade dependency to Go 1.8.3. !31943
+- Add prometheus metrics on pipeline creation.
+- Fix etag route not being a match for environments.
+- Sort folder for environments.
+- Support descriptions for snippets.
+- Hide clone panel and file list when user is only a guest. (James Clark)
+- Don’t create comment on JIRA if it already exists for the entity.
+- Update Dashboard Groups UI with better support for subgroups.
+- Confirm Project forking behaviour via the API.
+- Add prometheus based metrics collection to gitlab webapp.
+- Fix: Wiki is not searchable with Guest permissions.
+- Center all empty states.
+- Remove 'New issue' button when issues search returns no results.
+- Add API URL to JIRA settings.
+- animate adding issue to boards.
+- Update session cookie key name to be unique to instance in development.
+- Single click on filter to open filtered search dropdown.
+- Makes header information of pipeline show page realtine.
+- Creates a mediator for pipeline details vue in order to mount several vue apps with the same data.
+- Scope issue/merge request recent searches to project.
+- Increase individual diff collapse limit to 100 KB, and render limit to 200 KB.
+- Fix Pipelines table empty state - only render empty state if we receive 0 pipelines.
+- Make New environment empty state btn lowercase.
+- Removes duplicate environment variable in documentation.
+- Change links in issuable meta to black.
+- Fix border-bottom for project activity tab.
+- Adds new icon for CI skipped status.
+- Create equal padding for emoji.
+- Use briefcase icon for company in profile page.
+- Remove overflow from comment form for confidential issues and vertically aligns confidential issue icon.
+- Keep trailing newline when resolving conflicts by picking sides.
+- Fix /unsubscribe slash command creating extra todos when you were already mentioned in an issue.
+- Fix math rendering on blob pages.
+- Allow group reporters to manage group labels.
+- Use pre-wrap for commit messages to keep lists indented.
+- Count badges depend on translucent color to better adjust to different background colors and permission badges now feature a pill shaped design similar to labels.
+- Allow reporters to promote project labels to group labels.
+- Enabled keyboard shortcuts on artifacts pages.
+- Perform filtered search when state tab is changed.
+- Remove duplication for sharing projects with groups in project settings.
+- Change order of commits ahead and behind on divergence graph for branch list view.
+- Creates CI Header component for Pipelines and Jobs details pages.
+- Invalidate cache for issue and MR counters more granularly.
+- disable blocked manual actions.
+- Load tree readme asynchronously.
+- Display extra info about files on .gitlab-ci.yml, .gitlab/route-map.yml and LICENSE blob pages.
+- Fix replying to a commit discussion displayed in the context of an MR.
+- Consistently use monospace font for commit SHAs and branch and tag names.
+- Consistently display last push event widget.
+- Don't copy empty elements that were not selected on purpose as GFM.
+- Copy as GFM even when parts of other elements are selected.
+- Autolink package names in Gemfile.
+- Resolve N+1 query issue with discussions.
+- Don't match email addresses or foo@bar as user references.
+- Fix title of discussion jump button at top of page.
+- Don't return nil for missing objects from parser cache.
+- Make .gitmodules parsing more resilient to syntax errors.
+- Add username parameter to gravatar URL.
+- Autolink package names in more dependency files.
+- Return nil when looking up config for unknown LDAP provider.
+- Add system note with link to diff comparison when MR discussion becomes outdated.
+- Don't wrap pasted code when it's already inside code tags.
+- Revert 'New file from interface on existing branch'.
+- Show last commit for current tree on tree page.
+- Add documentation about adding foreign keys.
+- add username field to push webhook. (David Turner)
+- Rename CI/CD Pipelines to Pipelines in the project settings.
+- Make environment tables responsive.
+- Expand/collapse backlog & closed lists in issue boards.
+- Fix GitHub importer performance on branch existence check.
+- Fix counter cache for acts as taggable.
+- Github - Fix token interpolation when cloning wiki repository.
+- Fix token interpolation when setting the Github remote.
+- Fix N+1 queries for non-members in comment threads.
+- Fix terminals support for Kubernetes Service.
+- Fix: A diff comment on a change at last line of a file shows as two comments in discussion.
+- Instrument MergeRequestDiff#load_commits.
+- Introduce source to Pipeline entity.
+- Fixed create new label form in issue form not working for sub-group projects.
+- Fixed style on unsubscribe page. (Gustav Ernberg)
+- Enables inline editing for an issues title & description.
+- Ask for an example project for bug reports.
+- Add summary lines for collapsed details in the bug report template.
+- Prevent commits from upstream repositories to be re-processed by forks.
+- Avoid repeated queries for pipeline builds on merge requests.
+- Preloads head pipeline for merge request collection.
+- Handle head pipeline when creating merge requests.
+- Migrate artifacts to a new path.
+- Rescue OpenSSL::SSL::SSLError in JiraService & IssueTrackerService.
+- Repository browser: handle in-repository submodule urls. (David Turner)
+- Prevent project transfers if a new group is not selected.
+- Allow 'no one' as an option for allowed to merge on a procted branch.
+- Reduce time spent waiting for certain Sidekiq jobs to complete.
+- Refactor ProjectsFinder#init_collection to produce more efficient queries for retrieving projects.
+- Remove unused code and uses underscore.
+- Restricts search projects dropdown to group projects when group is selected.
+- Properly handle container registry redirects to fix metadata stored on a S3 backend.
+- Fix LFS timeouts when trying to save large files.
+- Set artifact working directory to be in the destination store to prevent unnecessary I/O.
+- Strip trailing whitespaces in submodule URLs.
+- Make sure reCAPTCHA configuration is loaded when spam checks are initiated.
+- Fix up arrow not editing last discussion comment.
+- Added application readiness endpoints to the monitoring health check admin view.
+- Use wait_for_requests for both ajax and Vue requests.
+- Cleanup ci_variables schema and table.
+- Remove foreigh key on ci_trigger_schedules only if it exists.
+- Allow translation of Pipeline Schedules.
+
+## 9.2.7 (2017-06-21)
+
+- Reinstate is_admin flag in users api when authenticated user is an admin. !12211 (rickettm)
+
 ## 9.2.6 (2017-06-16)
 
 - Fix the last coverage in trace log should be extracted. !11128 (dosuken123)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8b6c87ae51870e253b0009a9ecee9ad8a96e32df..89e505709a30d7b02cc4463efa2d66a7880afeda 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -49,6 +49,8 @@ _This notice should stay as the first item in the CONTRIBUTING.MD file._
 Thank you for your interest in contributing to GitLab. This guide details how
 to contribute to GitLab in a way that is efficient for everyone.
 
+Looking for something to work on? Look for the label [Accepting Merge Requests](#i-want-to-contribute).
+
 GitLab comes into two flavors, GitLab Community Edition (CE) our free and open
 source edition, and GitLab Enterprise Edition (EE) which is our commercial
 edition. Throughout this guide you will see references to CE and EE for
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index bc859cbd6d998ecfd13ee4c49cd5da054afb36ad..a803cc227fe6ff1fbb6dcfc2dde3e4ccc450257e 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.11.2
+0.14.0
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index ab0fa336dd0af145c203ff7deb76a68f5b21ee90..ac14c3dfaa865ea332c62348d1bca867bdbbd1cf 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-5.0.5
+5.1.1
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index 3e3c2f1e5edb083aab93646ac7b076daa38516dd..ccbccc3dc62631f22ff358ac418e52401ec770b4 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-2.1.1
+2.2.0
diff --git a/Gemfile b/Gemfile
index 2c200f2fa7adbc0413a0c4b0bbdf6c6dd8caf569..7814de99fb28df276972473dd38aaac6edc8569d 100644
--- a/Gemfile
+++ b/Gemfile
@@ -2,7 +2,7 @@ source 'https://rubygems.org'
 
 gem 'rails', '4.2.8'
 gem 'rails-deprecated_sanitizer', '~> 1.0.3'
-gem 'bootsnap', '~> 1.0.0'
+gem 'bootsnap', '~> 1.1'
 
 # Responders respond_to and respond_with
 gem 'responders', '~> 2.0'
@@ -86,7 +86,7 @@ gem 'kaminari', '~> 0.17.0'
 gem 'hamlit', '~> 2.6.1'
 
 # Files attachments
-gem 'carrierwave', '~> 1.0'
+gem 'carrierwave', '~> 1.1'
 
 # Drag and Drop UI
 gem 'dropzonejs-rails', '~> 0.7.1'
@@ -123,6 +123,7 @@ gem 'asciidoctor', '~> 1.5.2'
 gem 'asciidoctor-plantuml', '0.0.7'
 gem 'rouge', '~> 2.0'
 gem 'truncato', '~> 0.7.8'
+gem 'bootstrap_form', '~> 2.7.0'
 
 # See https://groups.google.com/forum/#!topic/ruby-security-ann/aSbgDiwb24s
 # and https://groups.google.com/forum/#!topic/ruby-security-ann/Dy7YiKb_pMM
@@ -158,7 +159,7 @@ gem 'rufus-scheduler', '~> 3.4'
 gem 'httparty', '~> 0.13.3'
 
 # Colored output to console
-gem 'rainbow', '~> 2.1.0'
+gem 'rainbow', '~> 2.2'
 
 # GitLab settings
 gem 'settingslogic', '~> 2.0.9'
@@ -254,12 +255,13 @@ gem 'net-ssh', '~> 3.0.1'
 gem 'base32', '~> 0.3.0'
 
 # Sentry integration
-gem 'sentry-raven', '~> 2.4.0'
+gem 'sentry-raven', '~> 2.5.3'
 
-gem 'premailer-rails', '~> 1.9.0'
+gem 'premailer-rails', '~> 1.9.7'
 
 # I18n
 gem 'ruby_parser', '~> 3.8', require: false
+gem 'rails-i18n', '~> 4.0.9'
 gem 'gettext_i18n_rails', '~> 1.8.0'
 gem 'gettext_i18n_rails_js', '~> 1.2.0'
 gem 'gettext', '~> 3.2.2', require: false, group: :development
@@ -283,6 +285,7 @@ group :metrics do
 
   # Prometheus
   gem 'prometheus-client-mmap', '~>0.7.0.beta5'
+  gem 'raindrops', '~> 0.18'
 end
 
 group :development do
@@ -353,7 +356,7 @@ group :test do
   gem 'shoulda-matchers', '~> 2.8.0', require: false
   gem 'email_spec', '~> 1.6.0'
   gem 'json-schema', '~> 2.6.2'
-  gem 'webmock', '~> 1.24.0'
+  gem 'webmock', '~> 2.3.2'
   gem 'test_after_commit', '~> 1.1'
   gem 'sham_rack', '~> 1.3.6'
   gem 'timecop', '~> 0.8.0'
@@ -373,7 +376,7 @@ gem 'ruby-prof', '~> 0.16.2'
 gem 'oauth2', '~> 1.4'
 
 # Soft deletion
-gem 'paranoia', '~> 2.2'
+gem 'paranoia', '~> 2.3.1'
 
 # Health check
 gem 'health_check', '~> 2.6.0'
@@ -383,7 +386,7 @@ gem 'vmstat', '~> 2.3.0'
 gem 'sys-filesystem', '~> 1.1.6'
 
 # Gitaly GRPC client
-gem 'gitaly', '~> 0.8.0'
+gem 'gitaly', '~> 0.9.0'
 
 gem 'toml-rb', '~> 0.3.15', require: false
 
diff --git a/Gemfile.lock b/Gemfile.lock
index 6755c75e33167b47b197691ca9b97da563c5bd8a..70abc0669df04eca9ddd26a735465a321128a201 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -83,11 +83,12 @@ GEM
     bindata (2.3.5)
     binding_of_caller (0.7.2)
       debug_inspector (>= 0.0.1)
-    bootsnap (1.0.0)
+    bootsnap (1.1.1)
       msgpack (~> 1.0)
     bootstrap-sass (3.3.6)
       autoprefixer-rails (>= 5.2.1)
       sass (>= 3.3.4)
+    bootstrap_form (2.7.0)
     brakeman (3.6.1)
     browser (2.2.0)
     builder (3.2.3)
@@ -108,7 +109,7 @@ GEM
     capybara-screenshot (1.0.14)
       capybara (>= 1.0, < 3)
       launchy
-    carrierwave (1.0.0)
+    carrierwave (1.1.0)
       activemodel (>= 4.0.0)
       activesupport (>= 4.0.0)
       mime-types (>= 1.16)
@@ -138,7 +139,7 @@ GEM
     crack (0.4.3)
       safe_yaml (~> 1.0.0)
     creole (0.5.0)
-    css_parser (1.4.1)
+    css_parser (1.5.0)
       addressable
     d3_rails (3.5.11)
       railties (>= 3.1.0)
@@ -277,7 +278,7 @@ GEM
       po_to_json (>= 1.0.0)
       rails (>= 3.2.0)
     gherkin-ruby (0.3.2)
-    gitaly (0.8.0)
+    gitaly (0.9.0)
       google-protobuf (~> 3.1)
       grpc (~> 1.0)
     github-linguist (4.7.6)
@@ -353,7 +354,7 @@ GEM
     grape-entity (0.6.0)
       activesupport
       multi_json (>= 1.3.2)
-    grpc (1.2.5)
+    grpc (1.4.0)
       google-protobuf (~> 3.1)
       googleauth (~> 0.5.1)
     haml (4.0.7)
@@ -367,7 +368,7 @@ GEM
       temple (~> 0.7.6)
       thor
       tilt
-    hashdiff (0.3.2)
+    hashdiff (0.3.4)
     hashie (3.5.5)
     hashie-forbidden_attributes (0.1.1)
       hashie (>= 3.0)
@@ -462,7 +463,7 @@ GEM
     mimemagic (0.3.0)
     mini_portile2 (2.1.0)
     minitest (5.7.0)
-    mmap2 (2.2.6)
+    mmap2 (2.2.7)
     mousetrap-rails (1.4.6)
     msgpack (1.1.0)
     multi_json (1.12.1)
@@ -546,8 +547,8 @@ GEM
       rubypants (~> 0.2)
     orm_adapter (0.5.0)
     os (0.9.6)
-    paranoia (2.2.0)
-      activerecord (>= 4.0, < 5.1)
+    paranoia (2.3.1)
+      activerecord (>= 4.0, < 5.2)
     parser (2.4.0.0)
       ast (~> 2.2)
     path_expander (1.0.1)
@@ -591,14 +592,15 @@ GEM
       websocket-driver (>= 0.2.0)
     posix-spawn (0.3.11)
     powerpack (0.1.1)
-    premailer (1.8.6)
-      css_parser (>= 1.3.6)
+    premailer (1.10.4)
+      addressable
+      css_parser (>= 1.4.10)
       htmlentities (>= 4.0.0)
-    premailer-rails (1.9.2)
+    premailer-rails (1.9.7)
       actionmailer (>= 3, < 6)
       premailer (~> 1.7, >= 1.7.9)
-    prometheus-client-mmap (0.7.0.beta5)
-      mmap2 (~> 2.2.6)
+    prometheus-client-mmap (0.7.0.beta8)
+      mmap2 (~> 2.2, >= 2.2.7)
     pry (0.10.4)
       coderay (~> 1.1.0)
       method_source (~> 0.8.1)
@@ -646,13 +648,17 @@ GEM
       rails-deprecated_sanitizer (>= 1.0.1)
     rails-html-sanitizer (1.0.3)
       loofah (~> 2.0)
+    rails-i18n (4.0.9)
+      i18n (~> 0.7)
+      railties (~> 4.0)
     railties (4.2.8)
       actionpack (= 4.2.8)
       activesupport (= 4.2.8)
       rake (>= 0.8.7)
       thor (>= 0.18.1, < 2.0)
-    rainbow (2.1.0)
-    raindrops (0.17.0)
+    rainbow (2.2.2)
+      rake
+    raindrops (0.18.0)
     rake (10.5.0)
     rblineprof (0.3.6)
       debugger-ruby_core_source (~> 1.3)
@@ -769,7 +775,7 @@ GEM
       activesupport (>= 3.1)
     select2-rails (3.5.9.3)
       thor (~> 0.14)
-    sentry-raven (2.4.0)
+    sentry-raven (2.5.3)
       faraday (>= 0.7.6, < 1.0)
     settingslogic (2.0.9)
     sexp_processor (4.9.0)
@@ -885,7 +891,7 @@ GEM
     vmstat (2.3.0)
     warden (1.2.6)
       rack (>= 1.0)
-    webmock (1.24.6)
+    webmock (2.3.2)
       addressable (>= 2.3.6)
       crack (>= 0.3.2)
       hashdiff
@@ -924,15 +930,16 @@ DEPENDENCIES
   benchmark-ips (~> 2.3.0)
   better_errors (~> 2.1.0)
   binding_of_caller (~> 0.7.2)
-  bootsnap (~> 1.0.0)
+  bootsnap (~> 1.1)
   bootstrap-sass (~> 3.3.0)
+  bootstrap_form (~> 2.7.0)
   brakeman (~> 3.6.0)
   browser (~> 2.2)
   bullet (~> 5.5.0)
   bundler-audit (~> 0.5.0)
   capybara (~> 2.6.2)
   capybara-screenshot (~> 1.0.0)
-  carrierwave (~> 1.0)
+  carrierwave (~> 1.1)
   charlock_holmes (~> 0.7.3)
   chronic (~> 0.10.2)
   chronic_duration (~> 0.10.6)
@@ -973,7 +980,7 @@ DEPENDENCIES
   gettext (~> 3.2.2)
   gettext_i18n_rails (~> 1.8.0)
   gettext_i18n_rails_js (~> 1.2.0)
-  gitaly (~> 0.8.0)
+  gitaly (~> 0.9.0)
   github-linguist (~> 4.7.0)
   gitlab-flowdock-git-hook (~> 1.0.1)
   gitlab-markup (~> 1.5.1)
@@ -1031,7 +1038,7 @@ DEPENDENCIES
   omniauth-twitter (~> 1.2.0)
   omniauth_crowd (~> 2.2.0)
   org-ruby (~> 0.9.12)
-  paranoia (~> 2.2)
+  paranoia (~> 2.3.1)
   peek (~> 1.0.1)
   peek-gc (~> 0.0.2)
   peek-host (~> 1.0.0)
@@ -1043,7 +1050,7 @@ DEPENDENCIES
   peek-sidekiq (~> 1.0.3)
   pg (~> 0.18.2)
   poltergeist (~> 1.9.0)
-  premailer-rails (~> 1.9.0)
+  premailer-rails (~> 1.9.7)
   prometheus-client-mmap (~> 0.7.0.beta5)
   pry-byebug (~> 3.4.1)
   pry-rails (~> 0.3.4)
@@ -1053,7 +1060,9 @@ DEPENDENCIES
   rack-proxy (~> 0.6.0)
   rails (= 4.2.8)
   rails-deprecated_sanitizer (~> 1.0.3)
-  rainbow (~> 2.1.0)
+  rails-i18n (~> 4.0.9)
+  rainbow (~> 2.2)
+  raindrops (~> 0.18)
   rblineprof (~> 0.3.6)
   rdoc (~> 4.2)
   recaptcha (~> 3.0)
@@ -1081,7 +1090,7 @@ DEPENDENCIES
   scss_lint (~> 0.47.0)
   seed-fu (~> 2.3.5)
   select2-rails (~> 3.5.9)
-  sentry-raven (~> 2.4.0)
+  sentry-raven (~> 2.5.3)
   settingslogic (~> 2.0.9)
   sham_rack (~> 1.3.6)
   shoulda-matchers (~> 2.8.0)
@@ -1114,9 +1123,9 @@ DEPENDENCIES
   version_sorter (~> 2.1.0)
   virtus (~> 1.0.1)
   vmstat (~> 2.3.0)
-  webmock (~> 1.24.0)
+  webmock (~> 2.3.2)
   webpack-rails (~> 0.9.10)
   wikicloth (= 0.8.1)
 
 BUNDLED WITH
-   1.15.0
+   1.15.1
diff --git a/app/assets/images/new_nav.png b/app/assets/images/new_nav.png
new file mode 100644
index 0000000000000000000000000000000000000000..8879d26d341186a1161a7dd1d83f108ad9fe5b12
Binary files /dev/null and b/app/assets/images/new_nav.png differ
diff --git a/app/assets/images/old_nav.png b/app/assets/images/old_nav.png
new file mode 100644
index 0000000000000000000000000000000000000000..23fae7aa19e9f20fd8d258bcea1196844b3b3f08
Binary files /dev/null and b/app/assets/images/old_nav.png differ
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index adb45b0606dd43ac23093774259886b10a41b342..18cd04b176a43b352473140d763688e5a10f1904 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -1,12 +1,8 @@
+/* eslint-disable class-methods-use-this */
 /* global Flash */
 
 import Cookies from 'js-cookie';
 
-import emojiMap from 'emojis/digests.json';
-import emojiAliases from 'emojis/aliases.json';
-import { glEmojiTag } from './behaviors/gl_emoji';
-import isEmojiNameValid from './behaviors/gl_emoji/is_emoji_name_valid';
-
 const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd';
 const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd';
 const requestAnimationFrame = window.requestAnimationFrame ||
@@ -16,8 +12,6 @@ const requestAnimationFrame = window.requestAnimationFrame ||
 
 const FROM_SENTENCE_REGEX = /(?:, and | and |, )/; // For separating lists produced by ruby's Array#toSentence
 
-let categoryMap = null;
-
 const categoryLabelMap = {
   activity: 'Activity',
   people: 'People',
@@ -29,186 +23,144 @@ const categoryLabelMap = {
   flags: 'Flags',
 };
 
-function buildCategoryMap() {
-  return Object.keys(emojiMap).reduce((currentCategoryMap, emojiNameKey) => {
-    const emojiInfo = emojiMap[emojiNameKey];
-    if (currentCategoryMap[emojiInfo.category]) {
-      currentCategoryMap[emojiInfo.category].push(emojiNameKey);
-    }
-
-    return currentCategoryMap;
-  }, {
-    activity: [],
-    people: [],
-    nature: [],
-    food: [],
-    travel: [],
-    objects: [],
-    symbols: [],
-    flags: [],
-  });
-}
-
-function renderCategory(name, emojiList, opts = {}) {
-  return `
-    <h5 class="emoji-menu-title">
-      ${name}
-    </h5>
-    <ul class="clearfix emoji-menu-list ${opts.menuListClass || ''}">
-      ${emojiList.map(emojiName => `
-        <li class="emoji-menu-list-item">
-          <button class="emoji-menu-btn text-center js-emoji-btn" type="button">
-            ${glEmojiTag(emojiName, {
-              sprite: true,
-            })}
-          </button>
-        </li>
-      `).join('\n')}
-    </ul>
-  `;
-}
+class AwardsHandler {
+  constructor(emoji) {
+    this.emoji = emoji;
+    this.eventListeners = [];
+    // If the user shows intent let's pre-build the menu
+    this.registerEventListener('one', $(document), 'mouseenter focus', '.js-add-award', 'mouseenter focus', () => {
+      const $menu = $('.emoji-menu');
+      if ($menu.length === 0) {
+        requestAnimationFrame(() => {
+          this.createEmojiMenu();
+        });
+      }
+    });
+    this.registerEventListener('on', $(document), 'click', '.js-add-award', (e) => {
+      e.stopPropagation();
+      e.preventDefault();
+      this.showEmojiMenu($(e.currentTarget));
+    });
 
-function AwardsHandler() {
-  this.eventListeners = [];
-  this.aliases = emojiAliases;
-  // If the user shows intent let's pre-build the menu
-  this.registerEventListener('one', $(document), 'mouseenter focus', '.js-add-award', 'mouseenter focus', () => {
-    const $menu = $('.emoji-menu');
-    if ($menu.length === 0) {
-      requestAnimationFrame(() => {
-        this.createEmojiMenu();
-      });
-    }
-    // Prebuild the categoryMap
-    categoryMap = categoryMap || buildCategoryMap();
-  });
-  this.registerEventListener('on', $(document), 'click', '.js-add-award', (e) => {
-    e.stopPropagation();
-    e.preventDefault();
-    this.showEmojiMenu($(e.currentTarget));
-  });
-
-  this.registerEventListener('on', $('html'), 'click', (e) => {
-    const $target = $(e.target);
-    if (!$target.closest('.emoji-menu-content').length) {
-      $('.js-awards-block.current').removeClass('current');
-    }
-    if (!$target.closest('.emoji-menu').length) {
-      if ($('.emoji-menu').is(':visible')) {
-        $('.js-add-award.is-active').removeClass('is-active');
-        $('.emoji-menu').removeClass('is-visible');
+    this.registerEventListener('on', $('html'), 'click', (e) => {
+      const $target = $(e.target);
+      if (!$target.closest('.emoji-menu-content').length) {
+        $('.js-awards-block.current').removeClass('current');
       }
-    }
-  });
-  this.registerEventListener('on', $(document), 'click', '.js-emoji-btn', (e) => {
-    e.preventDefault();
-    const $target = $(e.currentTarget);
-    const $glEmojiElement = $target.find('gl-emoji');
-    const $spriteIconElement = $target.find('.icon');
-    const emoji = ($glEmojiElement.length ? $glEmojiElement : $spriteIconElement).data('name');
-
-    $target.closest('.js-awards-block').addClass('current');
-    this.addAward(this.getVotesBlock(), this.getAwardUrl(), emoji);
-  });
-}
+      if (!$target.closest('.emoji-menu').length) {
+        if ($('.emoji-menu').is(':visible')) {
+          $('.js-add-award.is-active').removeClass('is-active');
+          $('.emoji-menu').removeClass('is-visible');
+        }
+      }
+    });
+    this.registerEventListener('on', $(document), 'click', '.js-emoji-btn', (e) => {
+      e.preventDefault();
+      const $target = $(e.currentTarget);
+      const $glEmojiElement = $target.find('gl-emoji');
+      const $spriteIconElement = $target.find('.icon');
+      const emojiName = ($glEmojiElement.length ? $glEmojiElement : $spriteIconElement).data('name');
+
+      $target.closest('.js-awards-block').addClass('current');
+      this.addAward(this.getVotesBlock(), this.getAwardUrl(), emojiName);
+    });
+  }
 
-AwardsHandler.prototype.registerEventListener = function registerEventListener(method = 'on', element, ...args) {
-  element[method].call(element, ...args);
-  this.eventListeners.push({
-    element,
-    args,
-  });
-};
+  registerEventListener(method = 'on', element, ...args) {
+    element[method].call(element, ...args);
+    this.eventListeners.push({
+      element,
+      args,
+    });
+  }
 
-AwardsHandler.prototype.showEmojiMenu = function showEmojiMenu($addBtn) {
-  if ($addBtn.hasClass('js-note-emoji')) {
-    $addBtn.closest('.note').find('.js-awards-block').addClass('current');
-  } else {
-    $addBtn.closest('.js-awards-block').addClass('current');
-  }
-
-  const $menu = $('.emoji-menu');
-  const $thumbsBtn = $menu.find('[data-name="thumbsup"], [data-name="thumbsdown"]').parent();
-  const $userAuthored = this.isUserAuthored($addBtn);
-  if ($menu.length) {
-    if ($menu.is('.is-visible')) {
-      $addBtn.removeClass('is-active');
-      $menu.removeClass('is-visible');
-      $('.js-emoji-menu-search').blur();
+  showEmojiMenu($addBtn) {
+    if ($addBtn.hasClass('js-note-emoji')) {
+      $addBtn.closest('.note').find('.js-awards-block').addClass('current');
     } else {
-      $addBtn.addClass('is-active');
-      this.positionMenu($menu, $addBtn);
-      $menu.addClass('is-visible');
-      $('.js-emoji-menu-search').focus();
+      $addBtn.closest('.js-awards-block').addClass('current');
     }
-  } else {
-    $addBtn.addClass('is-loading is-active');
-    this.createEmojiMenu(() => {
-      const $createdMenu = $('.emoji-menu');
-      $addBtn.removeClass('is-loading');
-      this.positionMenu($createdMenu, $addBtn);
-      return setTimeout(() => {
-        $createdMenu.addClass('is-visible');
-        $('.js-emoji-menu-search').focus();
-      }, 200);
-    });
-  }
 
-  $thumbsBtn.toggleClass('disabled', $userAuthored);
-};
+    const $menu = $('.emoji-menu');
+    const $thumbsBtn = $menu.find('[data-name="thumbsup"], [data-name="thumbsdown"]').parent();
+    const $userAuthored = this.isUserAuthored($addBtn);
+    if ($menu.length) {
+      if ($menu.is('.is-visible')) {
+        $addBtn.removeClass('is-active');
+        $menu.removeClass('is-visible');
+        $('.js-emoji-menu-search').blur();
+      } else {
+        $addBtn.addClass('is-active');
+        this.positionMenu($menu, $addBtn);
+        $menu.addClass('is-visible');
+        $('.js-emoji-menu-search').focus();
+      }
+    } else {
+      $addBtn.addClass('is-loading is-active');
+      this.createEmojiMenu(() => {
+        const $createdMenu = $('.emoji-menu');
+        $addBtn.removeClass('is-loading');
+        this.positionMenu($createdMenu, $addBtn);
+        return setTimeout(() => {
+          $createdMenu.addClass('is-visible');
+          $('.js-emoji-menu-search').focus();
+        }, 200);
+      });
+    }
 
-// Create the emoji menu with the first category of emojis.
-// Then render the remaining categories of emojis one by one to avoid jank.
-AwardsHandler.prototype.createEmojiMenu = function createEmojiMenu(callback) {
-  if (this.isCreatingEmojiMenu) {
-    return;
-  }
-  this.isCreatingEmojiMenu = true;
-
-  // Render the first category
-  categoryMap = categoryMap || buildCategoryMap();
-  const categoryNameKey = Object.keys(categoryMap)[0];
-  const emojisInCategory = categoryMap[categoryNameKey];
-  const firstCategory = renderCategory(categoryLabelMap[categoryNameKey], emojisInCategory);
-
-  // Render the frequently used
-  const frequentlyUsedEmojis = this.getFrequentlyUsedEmojis();
-  let frequentlyUsedCatgegory = '';
-  if (frequentlyUsedEmojis.length > 0) {
-    frequentlyUsedCatgegory = renderCategory('Frequently used', frequentlyUsedEmojis, {
-      menuListClass: 'frequent-emojis',
-    });
+    $thumbsBtn.toggleClass('disabled', $userAuthored);
   }
 
-  const emojiMenuMarkup = `
-    <div class="emoji-menu">
-      <input type="text" name="emoji-menu-search" value="" class="js-emoji-menu-search emoji-search search-input form-control" placeholder="Search emoji" />
+  // Create the emoji menu with the first category of emojis.
+  // Then render the remaining categories of emojis one by one to avoid jank.
+  createEmojiMenu(callback) {
+    if (this.isCreatingEmojiMenu) {
+      return;
+    }
+    this.isCreatingEmojiMenu = true;
+
+    // Render the first category
+    const categoryMap = this.emoji.getEmojiCategoryMap();
+    const categoryNameKey = Object.keys(categoryMap)[0];
+    const emojisInCategory = categoryMap[categoryNameKey];
+    const firstCategory = this.renderCategory(categoryLabelMap[categoryNameKey], emojisInCategory);
+
+    // Render the frequently used
+    const frequentlyUsedEmojis = this.getFrequentlyUsedEmojis();
+    let frequentlyUsedCatgegory = '';
+    if (frequentlyUsedEmojis.length > 0) {
+      frequentlyUsedCatgegory = this.renderCategory('Frequently used', frequentlyUsedEmojis, {
+        menuListClass: 'frequent-emojis',
+      });
+    }
+
+    const emojiMenuMarkup = `
+      <div class="emoji-menu">
+        <input type="text" name="emoji-menu-search" value="" class="js-emoji-menu-search emoji-search search-input form-control" placeholder="Search emoji" />
 
-      <div class="emoji-menu-content">
-        ${frequentlyUsedCatgegory}
-        ${firstCategory}
+        <div class="emoji-menu-content">
+          ${frequentlyUsedCatgegory}
+          ${firstCategory}
+        </div>
       </div>
-    </div>
-  `;
+    `;
 
-  document.body.insertAdjacentHTML('beforeend', emojiMenuMarkup);
+    document.body.insertAdjacentHTML('beforeend', emojiMenuMarkup);
 
-  this.addRemainingEmojiMenuCategories();
-  this.setupSearch();
-  if (callback) {
-    callback();
+    this.addRemainingEmojiMenuCategories();
+    this.setupSearch();
+    if (callback) {
+      callback();
+    }
   }
-};
 
-AwardsHandler
-  .prototype
-  .addRemainingEmojiMenuCategories = function addRemainingEmojiMenuCategories() {
+  addRemainingEmojiMenuCategories() {
     if (this.isAddingRemainingEmojiMenuCategories) {
       return;
     }
     this.isAddingRemainingEmojiMenuCategories = true;
 
-    categoryMap = categoryMap || buildCategoryMap();
+    const categoryMap = this.emoji.getEmojiCategoryMap();
 
     // Avoid the jank and render the remaining categories separately
     // This will take more time, but makes UI more responsive
@@ -220,7 +172,7 @@ AwardsHandler
         promiseChain.then(() =>
           new Promise((resolve) => {
             const emojisInCategory = categoryMap[categoryNameKey];
-            const categoryMarkup = renderCategory(
+            const categoryMarkup = this.renderCategory(
               categoryLabelMap[categoryNameKey],
               emojisInCategory,
             );
@@ -243,179 +195,186 @@ AwardsHandler
       emojiContentElement.insertAdjacentHTML('beforeend', '<p>We encountered an error while adding the remaining categories</p>');
       throw new Error(`Error occurred in addRemainingEmojiMenuCategories: ${err.message}`);
     });
-  };
-
-AwardsHandler.prototype.positionMenu = function positionMenu($menu, $addBtn) {
-  const position = $addBtn.data('position');
-  // The menu could potentially be off-screen or in a hidden overflow element
-  // So we position the element absolute in the body
-  const css = {
-    top: `${$addBtn.offset().top + $addBtn.outerHeight()}px`,
-  };
-  if (position === 'right') {
-    css.left = `${($addBtn.offset().left - $menu.outerWidth()) + 20}px`;
-    $menu.addClass('is-aligned-right');
-  } else {
-    css.left = `${$addBtn.offset().left}px`;
-    $menu.removeClass('is-aligned-right');
-  }
-  return $menu.css(css);
-};
+  }
 
-AwardsHandler.prototype.addAward = function addAward(
-  votesBlock,
-  awardUrl,
-  emoji,
-  checkMutuality,
-  callback,
-) {
-  const normalizedEmoji = this.normalizeEmojiName(emoji);
-  const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent();
-  this.postEmoji($emojiButton, awardUrl, normalizedEmoji, () => {
-    this.addAwardToEmojiBar(votesBlock, normalizedEmoji, checkMutuality);
-    return typeof callback === 'function' ? callback() : undefined;
-  });
-  $('.emoji-menu').removeClass('is-visible');
-  $('.js-add-award.is-active').removeClass('is-active');
-};
+  renderCategory(name, emojiList, opts = {}) {
+    return `
+      <h5 class="emoji-menu-title">
+        ${name}
+      </h5>
+      <ul class="clearfix emoji-menu-list ${opts.menuListClass || ''}">
+        ${emojiList.map(emojiName => `
+          <li class="emoji-menu-list-item">
+            <button class="emoji-menu-btn text-center js-emoji-btn" type="button">
+              ${this.emoji.glEmojiTag(emojiName, {
+                sprite: true,
+              })}
+            </button>
+          </li>
+        `).join('\n')}
+      </ul>
+    `;
+  }
 
-AwardsHandler.prototype.addAwardToEmojiBar = function addAwardToEmojiBar(
-  votesBlock,
-  emoji,
-  checkForMutuality,
-) {
-  if (checkForMutuality || checkForMutuality === null) {
-    this.checkMutuality(votesBlock, emoji);
-  }
-  this.addEmojiToFrequentlyUsedList(emoji);
-  const normalizedEmoji = this.normalizeEmojiName(emoji);
-  const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent();
-  if ($emojiButton.length > 0) {
-    if (this.isActive($emojiButton)) {
-      this.decrementCounter($emojiButton, normalizedEmoji);
+  positionMenu($menu, $addBtn) {
+    const position = $addBtn.data('position');
+    // The menu could potentially be off-screen or in a hidden overflow element
+    // So we position the element absolute in the body
+    const css = {
+      top: `${$addBtn.offset().top + $addBtn.outerHeight()}px`,
+    };
+    if (position === 'right') {
+      css.left = `${($addBtn.offset().left - $menu.outerWidth()) + 20}px`;
+      $menu.addClass('is-aligned-right');
     } else {
-      const counter = $emojiButton.find('.js-counter');
-      counter.text(parseInt(counter.text(), 10) + 1);
-      $emojiButton.addClass('active');
-      this.addYouToUserList(votesBlock, normalizedEmoji);
-      this.animateEmoji($emojiButton);
+      css.left = `${$addBtn.offset().left}px`;
+      $menu.removeClass('is-aligned-right');
     }
-  } else {
-    votesBlock.removeClass('hidden');
-    this.createEmoji(votesBlock, normalizedEmoji);
+    return $menu.css(css);
   }
-};
 
-AwardsHandler.prototype.getVotesBlock = function getVotesBlock() {
-  const currentBlock = $('.js-awards-block.current');
-  let resultantVotesBlock = currentBlock;
-  if (currentBlock.length === 0) {
-    resultantVotesBlock = $('.js-awards-block').eq(0);
+  addAward(votesBlock, awardUrl, emoji, checkMutuality, callback) {
+    const normalizedEmoji = this.emoji.normalizeEmojiName(emoji);
+    const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent();
+    this.postEmoji($emojiButton, awardUrl, normalizedEmoji, () => {
+      this.addAwardToEmojiBar(votesBlock, normalizedEmoji, checkMutuality);
+      return typeof callback === 'function' ? callback() : undefined;
+    });
+    $('.emoji-menu').removeClass('is-visible');
+    $('.js-add-award.is-active').removeClass('is-active');
   }
 
-  return resultantVotesBlock;
-};
+  addAwardToEmojiBar(votesBlock, emoji, checkForMutuality) {
+    if (checkForMutuality || checkForMutuality === null) {
+      this.checkMutuality(votesBlock, emoji);
+    }
+    this.addEmojiToFrequentlyUsedList(emoji);
+    const normalizedEmoji = this.emoji.normalizeEmojiName(emoji);
+    const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent();
+    if ($emojiButton.length > 0) {
+      if (this.isActive($emojiButton)) {
+        this.decrementCounter($emojiButton, normalizedEmoji);
+      } else {
+        const counter = $emojiButton.find('.js-counter');
+        counter.text(parseInt(counter.text(), 10) + 1);
+        $emojiButton.addClass('active');
+        this.addYouToUserList(votesBlock, normalizedEmoji);
+        this.animateEmoji($emojiButton);
+      }
+    } else {
+      votesBlock.removeClass('hidden');
+      this.createEmoji(votesBlock, normalizedEmoji);
+    }
+  }
 
-AwardsHandler.prototype.getAwardUrl = function getAwardUrl() {
-  return this.getVotesBlock().data('award-url');
-};
+  getVotesBlock() {
+    const currentBlock = $('.js-awards-block.current');
+    let resultantVotesBlock = currentBlock;
+    if (currentBlock.length === 0) {
+      resultantVotesBlock = $('.js-awards-block').eq(0);
+    }
 
-AwardsHandler.prototype.checkMutuality = function checkMutuality(votesBlock, emoji) {
-  const awardUrl = this.getAwardUrl();
-  if (emoji === 'thumbsup' || emoji === 'thumbsdown') {
-    const mutualVote = emoji === 'thumbsup' ? 'thumbsdown' : 'thumbsup';
-    const $emojiButton = votesBlock.find(`[data-name="${mutualVote}"]`).parent();
-    const isAlreadyVoted = $emojiButton.hasClass('active');
-    if (isAlreadyVoted) {
-      this.addAward(votesBlock, awardUrl, mutualVote, false);
+    return resultantVotesBlock;
+  }
+
+  getAwardUrl() {
+    return this.getVotesBlock().data('award-url');
+  }
+
+  checkMutuality(votesBlock, emoji) {
+    const awardUrl = this.getAwardUrl();
+    if (emoji === 'thumbsup' || emoji === 'thumbsdown') {
+      const mutualVote = emoji === 'thumbsup' ? 'thumbsdown' : 'thumbsup';
+      const $emojiButton = votesBlock.find(`[data-name="${mutualVote}"]`).parent();
+      const isAlreadyVoted = $emojiButton.hasClass('active');
+      if (isAlreadyVoted) {
+        this.addAward(votesBlock, awardUrl, mutualVote, false);
+      }
     }
   }
-};
 
-AwardsHandler.prototype.isActive = function isActive($emojiButton) {
-  return $emojiButton.hasClass('active');
-};
+  isActive($emojiButton) {
+    return $emojiButton.hasClass('active');
+  }
 
-AwardsHandler.prototype.isUserAuthored = function isUserAuthored($button) {
-  return $button.hasClass('js-user-authored');
-};
+  isUserAuthored($button) {
+    return $button.hasClass('js-user-authored');
+  }
 
-AwardsHandler.prototype.decrementCounter = function decrementCounter($emojiButton, emoji) {
-  const counter = $('.js-counter', $emojiButton);
-  const counterNumber = parseInt(counter.text(), 10);
-  if (counterNumber > 1) {
-    counter.text(counterNumber - 1);
-    this.removeYouFromUserList($emojiButton);
-  } else if (emoji === 'thumbsup' || emoji === 'thumbsdown') {
-    $emojiButton.tooltip('destroy');
-    counter.text('0');
-    this.removeYouFromUserList($emojiButton);
-    if ($emojiButton.parents('.note').length) {
+  decrementCounter($emojiButton, emoji) {
+    const counter = $('.js-counter', $emojiButton);
+    const counterNumber = parseInt(counter.text(), 10);
+    if (counterNumber > 1) {
+      counter.text(counterNumber - 1);
+      this.removeYouFromUserList($emojiButton);
+    } else if (emoji === 'thumbsup' || emoji === 'thumbsdown') {
+      $emojiButton.tooltip('destroy');
+      counter.text('0');
+      this.removeYouFromUserList($emojiButton);
+      if ($emojiButton.parents('.note').length) {
+        this.removeEmoji($emojiButton);
+      }
+    } else {
       this.removeEmoji($emojiButton);
     }
-  } else {
-    this.removeEmoji($emojiButton);
+    return $emojiButton.removeClass('active');
   }
-  return $emojiButton.removeClass('active');
-};
 
-AwardsHandler.prototype.removeEmoji = function removeEmoji($emojiButton) {
-  $emojiButton.tooltip('destroy');
-  $emojiButton.remove();
-  const $votesBlock = this.getVotesBlock();
-  if ($votesBlock.find('.js-emoji-btn').length === 0) {
-    $votesBlock.addClass('hidden');
+  removeEmoji($emojiButton) {
+    $emojiButton.tooltip('destroy');
+    $emojiButton.remove();
+    const $votesBlock = this.getVotesBlock();
+    if ($votesBlock.find('.js-emoji-btn').length === 0) {
+      $votesBlock.addClass('hidden');
+    }
   }
-};
 
-AwardsHandler.prototype.getAwardTooltip = function getAwardTooltip($awardBlock) {
-  return $awardBlock.attr('data-original-title') || $awardBlock.attr('data-title') || '';
-};
-
-AwardsHandler.prototype.toSentence = function toSentence(list) {
-  let sentence;
-  if (list.length <= 2) {
-    sentence = list.join(' and ');
-  } else {
-    sentence = `${list.slice(0, -1).join(', ')}, and ${list[list.length - 1]}`;
+  getAwardTooltip($awardBlock) {
+    return $awardBlock.attr('data-original-title') || $awardBlock.attr('data-title') || '';
   }
 
-  return sentence;
-};
+  toSentence(list) {
+    let sentence;
+    if (list.length <= 2) {
+      sentence = list.join(' and ');
+    } else {
+      sentence = `${list.slice(0, -1).join(', ')}, and ${list[list.length - 1]}`;
+    }
 
-AwardsHandler.prototype.removeYouFromUserList = function removeYouFromUserList($emojiButton) {
-  const awardBlock = $emojiButton;
-  const originalTitle = this.getAwardTooltip(awardBlock);
-  const authors = originalTitle.split(FROM_SENTENCE_REGEX);
-  authors.splice(authors.indexOf('You'), 1);
-  return awardBlock
-    .closest('.js-emoji-btn')
-    .removeData('title')
-    .removeAttr('data-title')
-    .removeAttr('data-original-title')
-    .attr('title', this.toSentence(authors))
-    .tooltip('fixTitle');
-};
+    return sentence;
+  }
 
-AwardsHandler.prototype.addYouToUserList = function addYouToUserList(votesBlock, emoji) {
-  const awardBlock = this.findEmojiIcon(votesBlock, emoji).parent();
-  const origTitle = this.getAwardTooltip(awardBlock);
-  let users = [];
-  if (origTitle) {
-    users = origTitle.trim().split(FROM_SENTENCE_REGEX);
-  }
-  users.unshift('You');
-  return awardBlock
-    .attr('title', this.toSentence(users))
-    .tooltip('fixTitle');
-};
+  removeYouFromUserList($emojiButton) {
+    const awardBlock = $emojiButton;
+    const originalTitle = this.getAwardTooltip(awardBlock);
+    const authors = originalTitle.split(FROM_SENTENCE_REGEX);
+    authors.splice(authors.indexOf('You'), 1);
+    return awardBlock
+      .closest('.js-emoji-btn')
+      .removeData('title')
+      .removeAttr('data-title')
+      .removeAttr('data-original-title')
+      .attr('title', this.toSentence(authors))
+      .tooltip('fixTitle');
+  }
+
+  addYouToUserList(votesBlock, emoji) {
+    const awardBlock = this.findEmojiIcon(votesBlock, emoji).parent();
+    const origTitle = this.getAwardTooltip(awardBlock);
+    let users = [];
+    if (origTitle) {
+      users = origTitle.trim().split(FROM_SENTENCE_REGEX);
+    }
+    users.unshift('You');
+    return awardBlock
+      .attr('title', this.toSentence(users))
+      .tooltip('fixTitle');
+  }
 
-AwardsHandler
-  .prototype
-  .createAwardButtonForVotesBlock = function createAwardButtonForVotesBlock(votesBlock, emojiName) {
+  createAwardButtonForVotesBlock(votesBlock, emojiName) {
     const buttonHtml = `
       <button class="btn award-control js-emoji-btn has-tooltip active" title="You" data-placement="bottom">
-        ${glEmojiTag(emojiName)}
+        ${this.emoji.glEmojiTag(emojiName)}
         <span class="award-control-text js-counter">1</span>
       </button>
     `;
@@ -424,144 +383,136 @@ AwardsHandler
     this.animateEmoji($emojiButton);
     $('.award-control').tooltip();
     votesBlock.removeClass('current');
-  };
-
-AwardsHandler.prototype.animateEmoji = function animateEmoji($emoji) {
-  const className = 'pulse animated once short';
-  $emoji.addClass(className);
+  }
 
-  this.registerEventListener('on', $emoji, animationEndEventString, (e) => {
-    $(e.currentTarget).removeClass(className);
-  });
-};
+  animateEmoji($emoji) {
+    const className = 'pulse animated once short';
+    $emoji.addClass(className);
 
-AwardsHandler.prototype.createEmoji = function createEmoji(votesBlock, emoji) {
-  if ($('.emoji-menu').length) {
-    this.createAwardButtonForVotesBlock(votesBlock, emoji);
+    this.registerEventListener('on', $emoji, animationEndEventString, (e) => {
+      $(e.currentTarget).removeClass(className);
+    });
   }
-  this.createEmojiMenu(() => {
-    this.createAwardButtonForVotesBlock(votesBlock, emoji);
-  });
-};
 
-AwardsHandler.prototype.postEmoji = function postEmoji($emojiButton, awardUrl, emoji, callback) {
-  if (this.isUserAuthored($emojiButton)) {
-    this.userAuthored($emojiButton);
-  } else {
-    $.post(awardUrl, {
-      name: emoji,
-    }, (data) => {
-      if (data.ok) {
-        callback();
-      }
-    }).fail(() => new Flash('Something went wrong on our end.'));
+  createEmoji(votesBlock, emoji) {
+    if ($('.emoji-menu').length) {
+      this.createAwardButtonForVotesBlock(votesBlock, emoji);
+    }
+    this.createEmojiMenu(() => {
+      this.createAwardButtonForVotesBlock(votesBlock, emoji);
+    });
   }
-};
 
-AwardsHandler.prototype.findEmojiIcon = function findEmojiIcon(votesBlock, emoji) {
-  return votesBlock.find(`.js-emoji-btn [data-name="${emoji}"]`);
-};
+  postEmoji($emojiButton, awardUrl, emoji, callback) {
+    if (this.isUserAuthored($emojiButton)) {
+      this.userAuthored($emojiButton);
+    } else {
+      $.post(awardUrl, {
+        name: emoji,
+      }, (data) => {
+        if (data.ok) {
+          callback();
+        }
+      }).fail(() => new Flash('Something went wrong on our end.'));
+    }
+  }
 
-AwardsHandler.prototype.userAuthored = function userAuthored($emojiButton) {
-  const oldTitle = this.getAwardTooltip($emojiButton);
-  const newTitle = 'You cannot vote on your own issue, MR and note';
-  gl.utils.updateTooltipTitle($emojiButton, newTitle).tooltip('show');
-  // Restore tooltip back to award list
-  return setTimeout(() => {
-    $emojiButton.tooltip('hide');
-    gl.utils.updateTooltipTitle($emojiButton, oldTitle);
-  }, 2800);
-};
+  findEmojiIcon(votesBlock, emoji) {
+    return votesBlock.find(`.js-emoji-btn [data-name="${emoji}"]`);
+  }
 
-AwardsHandler.prototype.scrollToAwards = function scrollToAwards() {
-  const options = {
-    scrollTop: $('.awards').offset().top - 110,
-  };
-  return $('body, html').animate(options, 200);
-};
+  userAuthored($emojiButton) {
+    const oldTitle = this.getAwardTooltip($emojiButton);
+    const newTitle = 'You cannot vote on your own issue, MR and note';
+    gl.utils.updateTooltipTitle($emojiButton, newTitle).tooltip('show');
+    // Restore tooltip back to award list
+    return setTimeout(() => {
+      $emojiButton.tooltip('hide');
+      gl.utils.updateTooltipTitle($emojiButton, oldTitle);
+    }, 2800);
+  }
 
-AwardsHandler.prototype.normalizeEmojiName = function normalizeEmojiName(emoji) {
-  return Object.prototype.hasOwnProperty.call(this.aliases, emoji) ? this.aliases[emoji] : emoji;
-};
+  scrollToAwards() {
+    const options = {
+      scrollTop: $('.awards').offset().top - 110,
+    };
+    return $('body, html').animate(options, 200);
+  }
 
-AwardsHandler
-  .prototype
-  .addEmojiToFrequentlyUsedList = function addEmojiToFrequentlyUsedList(emoji) {
-    if (isEmojiNameValid(emoji)) {
+  addEmojiToFrequentlyUsedList(emoji) {
+    if (this.emoji.isEmojiNameValid(emoji)) {
       this.frequentlyUsedEmojis = _.uniq(this.getFrequentlyUsedEmojis().concat(emoji));
       Cookies.set('frequently_used_emojis', this.frequentlyUsedEmojis.join(','), { expires: 365 });
     }
-  };
-
-AwardsHandler.prototype.getFrequentlyUsedEmojis = function getFrequentlyUsedEmojis() {
-  return this.frequentlyUsedEmojis || (() => {
-    const frequentlyUsedEmojis = _.uniq((Cookies.get('frequently_used_emojis') || '').split(','));
-    this.frequentlyUsedEmojis = frequentlyUsedEmojis.filter(
-      inputName => isEmojiNameValid(inputName),
-    );
+  }
 
-    return this.frequentlyUsedEmojis;
-  })();
-};
+  getFrequentlyUsedEmojis() {
+    return this.frequentlyUsedEmojis || (() => {
+      const frequentlyUsedEmojis = _.uniq((Cookies.get('frequently_used_emojis') || '').split(','));
+      this.frequentlyUsedEmojis = frequentlyUsedEmojis.filter(
+        inputName => this.emoji.isEmojiNameValid(inputName),
+      );
 
-AwardsHandler.prototype.setupSearch = function setupSearch() {
-  const $search = $('.js-emoji-menu-search');
+      return this.frequentlyUsedEmojis;
+    })();
+  }
 
-  this.registerEventListener('on', $search, 'input', (e) => {
-    const term = $(e.target).val().trim();
-    this.searchEmojis(term);
-  });
+  setupSearch() {
+    const $search = $('.js-emoji-menu-search');
 
-  const $menu = $('.emoji-menu');
-  this.registerEventListener('on', $menu, transitionEndEventString, (e) => {
-    if (e.target === e.currentTarget) {
-      // Clear the search
-      this.searchEmojis('');
-    }
-  });
-};
+    this.registerEventListener('on', $search, 'input', (e) => {
+      const term = $(e.target).val().trim();
+      this.searchEmojis(term);
+    });
 
-AwardsHandler.prototype.searchEmojis = function searchEmojis(term) {
-  const $search = $('.js-emoji-menu-search');
-  $search.val(term);
-
-  // Clean previous search results
-  $('ul.emoji-menu-search, h5.emoji-search-title').remove();
-  if (term.length > 0) {
-    // Generate a search result block
-    const h5 = $('<h5 class="emoji-search-title"/>').text('Search results');
-    const foundEmojis = this.findMatchingEmojiElements(term).show();
-    const ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(foundEmojis);
-    $('.emoji-menu-content ul, .emoji-menu-content h5').hide();
-    $('.emoji-menu-content').append(h5).append(ul);
-  } else {
-    $('.emoji-menu-content').children().show();
+    const $menu = $('.emoji-menu');
+    this.registerEventListener('on', $menu, transitionEndEventString, (e) => {
+      if (e.target === e.currentTarget) {
+        // Clear the search
+        this.searchEmojis('');
+      }
+    });
   }
-};
-
-AwardsHandler.prototype.findMatchingEmojiElements = function findMatchingEmojiElements(term) {
-  const safeTerm = term.toLowerCase();
 
-  const namesMatchingAlias = [];
-  Object.keys(emojiAliases).forEach((alias) => {
-    if (alias.indexOf(safeTerm) >= 0) {
-      namesMatchingAlias.push(emojiAliases[alias]);
+  searchEmojis(term) {
+    const $search = $('.js-emoji-menu-search');
+    $search.val(term);
+
+    // Clean previous search results
+    $('ul.emoji-menu-search, h5.emoji-search-title').remove();
+    if (term.length > 0) {
+      // Generate a search result block
+      const h5 = $('<h5 class="emoji-search-title"/>').text('Search results');
+      const foundEmojis = this.findMatchingEmojiElements(term).show();
+      const ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(foundEmojis);
+      $('.emoji-menu-content ul, .emoji-menu-content h5').hide();
+      $('.emoji-menu-content').append(h5).append(ul);
+    } else {
+      $('.emoji-menu-content').children().show();
     }
-  });
-  const $matchingElements = namesMatchingAlias.concat(safeTerm)
-    .reduce(
-      ($result, searchTerm) =>
-        $result.add($(`.emoji-menu-list:not(.frequent-emojis) [data-name*="${searchTerm}"]`)),
-      $([]),
-    );
-  return $matchingElements.closest('li').clone();
-};
+  }
 
-AwardsHandler.prototype.destroy = function destroy() {
-  this.eventListeners.forEach((entry) => {
-    entry.element.off.call(entry.element, ...entry.args);
-  });
-  $('.emoji-menu').remove();
-};
+  findMatchingEmojiElements(query) {
+    const emojiMatches = this.emoji.filterEmojiNamesByAlias(query);
+    const $emojiElements = $('.emoji-menu-list:not(.frequent-emojis) [data-name]');
+    const $matchingElements = $emojiElements
+      .filter((i, elm) => emojiMatches.indexOf(elm.dataset.name) >= 0);
+    return $matchingElements.closest('li').clone();
+  }
+
+  destroy() {
+    this.eventListeners.forEach((entry) => {
+      entry.element.off.call(entry.element, ...entry.args);
+    });
+    $('.emoji-menu').remove();
+  }
+}
 
-export default AwardsHandler;
+let awardsHandlerPromise = null;
+export default function loadAwardsHandler(reload = false) {
+  if (!awardsHandlerPromise || reload) {
+    awardsHandlerPromise = import(/* webpackChunkName: 'emoji' */ './emoji')
+      .then(Emoji => new AwardsHandler(Emoji));
+  }
+  return awardsHandlerPromise;
+}
diff --git a/app/assets/javascripts/behaviors/autosize.js b/app/assets/javascripts/behaviors/autosize.js
index 3bea460dcc690b225b6adf3f35d58f09adda8926..e00af4b2fa8363304aedf1a6975550c8b913685c 100644
--- a/app/assets/javascripts/behaviors/autosize.js
+++ b/app/assets/javascripts/behaviors/autosize.js
@@ -1,23 +1,8 @@
 import autosize from 'vendor/autosize';
 
-$(() => {
-  const $fields = $('.js-autosize');
+document.addEventListener('DOMContentLoaded', () => {
+  const autosizeEls = document.querySelectorAll('.js-autosize');
 
-  $fields.on('autosize:resized', function resized() {
-    const $field = $(this);
-    $field.data('height', $field.outerHeight());
-  });
-
-  $fields.on('resize.autosize', function resize() {
-    const $field = $(this);
-    if ($field.data('height') !== $field.outerHeight()) {
-      $field.data('height', $field.outerHeight());
-      autosize.destroy($field);
-      $field.css('max-height', window.outerHeight);
-    }
-  });
-
-  autosize($fields);
-  autosize.update($fields);
-  $fields.css('resize', 'vertical');
+  autosize(autosizeEls);
+  autosize.update(autosizeEls);
 });
diff --git a/app/assets/javascripts/behaviors/gl_emoji.js b/app/assets/javascripts/behaviors/gl_emoji.js
index 36ce4fddb72d1ed24fc647510c7dfac2757df410..7e98e04303a427d9a34ec27eea2a4d505ad8222a 100644
--- a/app/assets/javascripts/behaviors/gl_emoji.js
+++ b/app/assets/javascripts/behaviors/gl_emoji.js
@@ -1,75 +1,9 @@
 import installCustomElements from 'document-register-element';
-import emojiMap from 'emojis/digests.json';
-import emojiAliases from 'emojis/aliases.json';
-import { getUnicodeSupportMap } from './gl_emoji/unicode_support_map';
-import { isEmojiUnicodeSupported } from './gl_emoji/is_emoji_unicode_supported';
+import isEmojiUnicodeSupported from '../emoji/support';
 
 installCustomElements(window);
 
-const generatedUnicodeSupportMap = getUnicodeSupportMap();
-
-function emojiImageTag(name, src) {
-  return `<img class="emoji" title=":${name}:" alt=":${name}:" src="${src}" width="20" height="20" align="absmiddle" />`;
-}
-
-function assembleFallbackImageSrc(inputName) {
-  let name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ?
-    emojiAliases[inputName] : inputName;
-  let emojiInfo = emojiMap[name];
-  // Fallback to question mark for unknown emojis
-  if (!emojiInfo) {
-    name = 'grey_question';
-    emojiInfo = emojiMap[name];
-  }
-  const fallbackImageSrc = `${gon.asset_host || ''}${gon.relative_url_root || ''}/assets/emoji/${name}-${emojiInfo.digest}.png`;
-
-  return fallbackImageSrc;
-}
-const glEmojiTagDefaults = {
-  sprite: false,
-  forceFallback: false,
-};
-function glEmojiTag(inputName, options) {
-  const opts = Object.assign({}, glEmojiTagDefaults, options);
-  let name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ?
-    emojiAliases[inputName] : inputName;
-  let emojiInfo = emojiMap[name];
-  // Fallback to question mark for unknown emojis
-  if (!emojiInfo) {
-    name = 'grey_question';
-    emojiInfo = emojiMap[name];
-  }
-
-  const fallbackImageSrc = assembleFallbackImageSrc(name);
-  const fallbackSpriteClass = `emoji-${name}`;
-
-  const classList = [];
-  if (opts.forceFallback && opts.sprite) {
-    classList.push('emoji-icon');
-    classList.push(fallbackSpriteClass);
-  }
-  const classAttribute = classList.length > 0 ? `class="${classList.join(' ')}"` : '';
-  const fallbackSpriteAttribute = opts.sprite ? `data-fallback-sprite-class="${fallbackSpriteClass}"` : '';
-  let contents = emojiInfo.moji;
-  if (opts.forceFallback && !opts.sprite) {
-    contents = emojiImageTag(name, fallbackImageSrc);
-  }
-
-  return `
-  <gl-emoji
-    ${classAttribute}
-    data-name="${name}"
-    data-fallback-src="${fallbackImageSrc}"
-    ${fallbackSpriteAttribute}
-    data-unicode-version="${emojiInfo.unicodeVersion}"
-    title="${emojiInfo.description}"
-  >
-    ${contents}
-  </gl-emoji>
-  `;
-}
-
-function installGlEmojiElement() {
+export default function installGlEmojiElement() {
   const GlEmojiElementProto = Object.create(HTMLElement.prototype);
   GlEmojiElementProto.createdCallback = function createdCallback() {
     const emojiUnicode = this.textContent.trim();
@@ -90,18 +24,26 @@ function installGlEmojiElement() {
     if (
       emojiUnicode &&
       isEmojiUnicode &&
-      !isEmojiUnicodeSupported(generatedUnicodeSupportMap, emojiUnicode, unicodeVersion)
+      !isEmojiUnicodeSupported(emojiUnicode, unicodeVersion)
     ) {
       // CSS sprite fallback takes precedence over image fallback
       if (hasCssSpriteFalback) {
         // IE 11 doesn't like adding multiple at once :(
         this.classList.add('emoji-icon');
         this.classList.add(fallbackSpriteClass);
-      } else if (hasImageFallback) {
-        this.innerHTML = emojiImageTag(name, fallbackSrc);
       } else {
-        const src = assembleFallbackImageSrc(name);
-        this.innerHTML = emojiImageTag(name, src);
+        import(/* webpackChunkName: 'emoji' */ '../emoji')
+          .then(({ emojiImageTag, emojiFallbackImageSrc }) => {
+            if (hasImageFallback) {
+              this.innerHTML = emojiImageTag(name, fallbackSrc);
+            } else {
+              const src = emojiFallbackImageSrc(name);
+              this.innerHTML = emojiImageTag(name, src);
+            }
+          })
+          .catch(() => {
+            // do nothing
+          });
       }
     }
   };
@@ -110,9 +52,3 @@ function installGlEmojiElement() {
     prototype: GlEmojiElementProto,
   });
 }
-
-export {
-  installGlEmojiElement,
-  glEmojiTag,
-  emojiImageTag,
-};
diff --git a/app/assets/javascripts/behaviors/gl_emoji/is_emoji_name_valid.js b/app/assets/javascripts/behaviors/gl_emoji/is_emoji_name_valid.js
deleted file mode 100644
index be4aeb32c467981bfcd2e2aa09cd2a9d72216bdd..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/behaviors/gl_emoji/is_emoji_name_valid.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import emojiMap from 'emojis/digests.json';
-import emojiAliases from 'emojis/aliases.json';
-
-function isEmojiNameValid(inputName) {
-  const name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ?
-    emojiAliases[inputName] : inputName;
-
-  return name && emojiMap[name];
-}
-
-export default isEmojiNameValid;
diff --git a/app/assets/javascripts/behaviors/index.js b/app/assets/javascripts/behaviors/index.js
index 5b931e6cfa6789a70afbb0ae5bdd6eb1aa6cf483..44b2c974b9ee78e965f54579eb546ceed8e790eb 100644
--- a/app/assets/javascripts/behaviors/index.js
+++ b/app/assets/javascripts/behaviors/index.js
@@ -1,7 +1,7 @@
 import './autosize';
 import './bind_in_out';
 import './details_behavior';
-import { installGlEmojiElement } from './gl_emoji';
+import installGlEmojiElement from './gl_emoji';
 import './quick_submit';
 import './requires_input';
 import './toggler_behavior';
diff --git a/app/assets/javascripts/behaviors/quick_submit.js b/app/assets/javascripts/behaviors/quick_submit.js
index 1f9e0448084511d876f6af7a952fd3d64d0db4b3..bc693616460a1046a3ec6aa50de3a576ac2702b6 100644
--- a/app/assets/javascripts/behaviors/quick_submit.js
+++ b/app/assets/javascripts/behaviors/quick_submit.js
@@ -40,7 +40,7 @@ $(document).on('keydown.quick_submit', '.js-quick-submit', (e) => {
 
   e.preventDefault();
   const $form = $(e.target).closest('form');
-  const $submitButton = $form.find('input[type=submit], button[type=submit]');
+  const $submitButton = $form.find('input[type=submit], button[type=submit]').first();
 
   if (!$submitButton.attr('disabled')) {
     $submitButton.trigger('click', [e]);
diff --git a/app/assets/javascripts/boards/components/board_new_issue.js b/app/assets/javascripts/boards/components/board_new_issue.js
index b1c47b09c35e08b8226ae1eee1d884918d70784d..4af8b0c7713800132ca85b1addc45481461ce5b3 100644
--- a/app/assets/javascripts/boards/components/board_new_issue.js
+++ b/app/assets/javascripts/boards/components/board_new_issue.js
@@ -17,7 +17,7 @@ export default {
   methods: {
     submit(e) {
       e.preventDefault();
-      if (this.title.trim() === '') return;
+      if (this.title.trim() === '') return Promise.resolve();
 
       this.error = false;
 
@@ -29,7 +29,10 @@ export default {
         assignees: [],
       });
 
-      this.list.newIssue(issue)
+      eventHub.$emit(`scroll-board-list-${this.list.id}`);
+      this.cancel();
+
+      return this.list.newIssue(issue)
         .then(() => {
           // Need this because our jQuery very kindly disables buttons on ALL form submissions
           $(this.$refs.submitButton).enable();
@@ -47,9 +50,6 @@ export default {
           // Show error message
           this.error = true;
         });
-
-      eventHub.$emit(`scroll-board-list-${this.list.id}`);
-      this.cancel();
     },
     cancel() {
       this.title = '';
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js
index c7afd4ead6bf6e7ae1fd73de48cf5af5c11f412c..590b7be36e399fc58dc91fb8cef3984b3eeda99b 100644
--- a/app/assets/javascripts/boards/components/board_sidebar.js
+++ b/app/assets/javascripts/boards/components/board_sidebar.js
@@ -34,7 +34,10 @@ gl.issueBoards.BoardSidebar = Vue.extend({
     },
     milestoneTitle() {
       return this.issue.milestone ? this.issue.milestone.title : 'No Milestone';
-    }
+    },
+    canRemove() {
+      return !this.list.preset;
+    },
   },
   watch: {
     detail: {
diff --git a/app/assets/javascripts/boards/components/sidebar/remove_issue.js b/app/assets/javascripts/boards/components/sidebar/remove_issue.js
index 5597f128b80db4aa078042afd11cf60820d8d4e9..6a900d4abd0ce2d323abe3eb2dc380bcf500aa6c 100644
--- a/app/assets/javascripts/boards/components/sidebar/remove_issue.js
+++ b/app/assets/javascripts/boards/components/sidebar/remove_issue.js
@@ -46,8 +46,7 @@ gl.issueBoards.RemoveIssueBtn = Vue.extend({
   },
   template: `
     <div
-      class="block list"
-      v-if="list.type !== 'closed'">
+      class="block list">
       <button
         class="btn btn-default btn-block"
         type="button"
diff --git a/app/assets/javascripts/boards/filtered_search_boards.js b/app/assets/javascripts/boards/filtered_search_boards.js
index b37698fe9caa399bcbc3d402988424eb394f3720..3f083655f950819300ac2b93633d8587ad644f17 100644
--- a/app/assets/javascripts/boards/filtered_search_boards.js
+++ b/app/assets/javascripts/boards/filtered_search_boards.js
@@ -11,7 +11,6 @@ export default class FilteredSearchBoards extends gl.FilteredSearchManager {
     // Issue boards is slightly different, we handle all the requests async
     // instead or reloading the page, we just re-fire the list ajax requests
     this.isHandledAsync = true;
-
     this.cantEdit = cantEdit;
   }
 
diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js
index 548de1a4c522a856e508818638dd0793716762a0..b4b09b3876e0673e399de489c4ac87c897070cfd 100644
--- a/app/assets/javascripts/boards/models/list.js
+++ b/app/assets/javascripts/boards/models/list.js
@@ -112,8 +112,7 @@ class List {
       .then((resp) => {
         const data = resp.json();
         issue.id = data.iid;
-      })
-      .then(() => {
+
         if (this.issuesSize > 1) {
           const moveBeforeIid = this.issues[1].id;
           gl.boardService.moveIssue(issue.id, null, null, null, moveBeforeIid);
diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js
index c28f6e151a007c17f56c995d4ba0cbc6e9ab0144..1dfa064acfd4edf735a778cf77fbb42fc0b16557 100644
--- a/app/assets/javascripts/build.js
+++ b/app/assets/javascripts/build.js
@@ -13,25 +13,21 @@ window.Build = (function () {
     this.options = options || $('.js-build-options').data();
 
     this.pageUrl = this.options.pageUrl;
-    this.buildUrl = this.options.buildUrl;
     this.buildStatus = this.options.buildStatus;
     this.state = this.options.logState;
     this.buildStage = this.options.buildStage;
     this.$document = $(document);
     this.logBytes = 0;
-    this.scrollOffsetPadding = 30;
     this.hasBeenScrolled = false;
 
     this.updateDropdown = this.updateDropdown.bind(this);
     this.getBuildTrace = this.getBuildTrace.bind(this);
-    this.scrollToBottom = this.scrollToBottom.bind(this);
 
-    this.$body = $('body');
     this.$buildTrace = $('#build-trace');
     this.$buildRefreshAnimation = $('.js-build-refresh');
     this.$truncatedInfo = $('.js-truncated-info');
     this.$buildTraceOutput = $('.js-build-output');
-    this.$scrollContainer = $('.js-scroll-container');
+    this.$topBar = $('.js-top-bar');
 
     // Scroll controllers
     this.$scrollTopBtn = $('.js-scroll-up');
@@ -63,13 +59,22 @@ window.Build = (function () {
       .off('click')
       .on('click', this.scrollToBottom.bind(this));
 
-    const scrollThrottled = _.throttle(this.toggleScroll.bind(this), 100);
+    this.scrollThrottled = _.throttle(this.toggleScroll.bind(this), 100);
 
-    this.$scrollContainer
+    $(window)
       .off('scroll')
       .on('scroll', () => {
-        this.hasBeenScrolled = true;
-        scrollThrottled();
+        const contentHeight = this.$buildTraceOutput.prop('scrollHeight');
+        if (contentHeight > this.windowSize) {
+          // means the user did not scroll, the content was updated.
+          this.windowSize = contentHeight;
+        } else {
+          // User scrolled
+          this.hasBeenScrolled = true;
+          this.toggleScrollAnimation(false);
+        }
+
+        this.scrollThrottled();
       });
 
     $(window)
@@ -77,60 +82,73 @@ window.Build = (function () {
       .on('resize.build', _.throttle(this.sidebarOnResize.bind(this), 100));
 
     this.updateArtifactRemoveDate();
+    this.initAffixTopArea();
 
-    // eslint-disable-next-line
-    this.getBuildTrace()
-      .then(() => this.toggleScroll())
-      .then(() => {
-        if (!this.hasBeenScrolled) {
-          this.scrollToBottom();
-        }
-      });
-
-    this.verifyTopPosition();
+    this.getBuildTrace();
   }
 
+  Build.prototype.initAffixTopArea = function () {
+    /**
+      If the browser does not support position sticky, it returns the position as static.
+      If the browser does support sticky, then we allow the browser to handle it, if not
+      then we default back to Bootstraps affix
+    **/
+    if (this.$topBar.css('position') !== 'static') return;
+
+    const offsetTop = this.$buildTrace.offset().top;
+
+    this.$topBar.affix({
+      offset: {
+        top: offsetTop,
+      },
+    });
+  };
+
   Build.prototype.canScroll = function () {
-    return (this.$scrollContainer.prop('scrollHeight') - this.scrollOffsetPadding) > this.$scrollContainer.height();
+    return document.body.scrollHeight > window.innerHeight;
   };
 
-  /**
-   * |                          | Up       | Down     |
-   * |--------------------------|----------|----------|
-   * | on scroll bottom         | active   | disabled |
-   * | on scroll top            | disabled | active   |
-   * | no scroll                | disabled | disabled |
-   * | on.('scroll') is on top  | disabled | active   |
-   * | on('scroll) is on bottom | active   | disabled |
-   *
-   */
   Build.prototype.toggleScroll = function () {
-    const currentPosition = this.$scrollContainer.scrollTop();
-    const bottomScroll = currentPosition + this.$scrollContainer.innerHeight();
+    const currentPosition = document.body.scrollTop;
+    const windowHeight = window.innerHeight;
 
     if (this.canScroll()) {
-      if (currentPosition === 0) {
+      if (currentPosition > 0 &&
+        (document.body.scrollHeight - currentPosition !== windowHeight)) {
+      // User is in the middle of the log
+
+        this.toggleDisableButton(this.$scrollTopBtn, false);
+        this.toggleDisableButton(this.$scrollBottomBtn, false);
+      } else if (currentPosition === 0) {
+        // User is at Top of Build Log
+
         this.toggleDisableButton(this.$scrollTopBtn, true);
         this.toggleDisableButton(this.$scrollBottomBtn, false);
-      } else if (bottomScroll === this.$scrollContainer.prop('scrollHeight')) {
+      } else if (document.body.scrollHeight - currentPosition === windowHeight) {
+        // User is at the bottom of the build log.
+
         this.toggleDisableButton(this.$scrollTopBtn, false);
         this.toggleDisableButton(this.$scrollBottomBtn, true);
-      } else {
-        this.toggleDisableButton(this.$scrollTopBtn, false);
-        this.toggleDisableButton(this.$scrollBottomBtn, false);
       }
+    } else {
+      this.toggleDisableButton(this.$scrollTopBtn, true);
+      this.toggleDisableButton(this.$scrollBottomBtn, true);
     }
   };
 
-  Build.prototype.scrollToTop = function () {
+  Build.prototype.scrollDown = function () {
+    document.body.scrollTop = document.body.scrollHeight;
+  };
+
+  Build.prototype.scrollToBottom = function () {
+    this.scrollDown();
     this.hasBeenScrolled = true;
-    this.$scrollContainer.scrollTop(0);
     this.toggleScroll();
   };
 
-  Build.prototype.scrollToBottom = function () {
+  Build.prototype.scrollToTop = function () {
+    document.body.scrollTop = 0;
     this.hasBeenScrolled = true;
-    this.$scrollContainer.scrollTop(this.$scrollContainer.prop('scrollHeight'));
     this.toggleScroll();
   };
 
@@ -143,47 +161,6 @@ window.Build = (function () {
     this.$scrollBottomBtn.toggleClass('animate', toggle);
   };
 
-  /**
-   * Build trace top position depends on the space ocupied by the elments rendered before
-   */
-  Build.prototype.verifyTopPosition = function () {
-    const $buildPage = $('.build-page');
-
-    const $flashError = $('.alert-wrapper');
-    const $header = $('.build-header', $buildPage);
-    const $runnersStuck = $('.js-build-stuck', $buildPage);
-    const $startsEnvironment = $('.js-environment-container', $buildPage);
-    const $erased = $('.js-build-erased', $buildPage);
-    const prependTopDefault = 20;
-
-    // header + navigation + margin
-    let topPostion = 168;
-
-    if ($header.length) {
-      topPostion += $header.outerHeight();
-    }
-
-    if ($runnersStuck.length) {
-      topPostion += $runnersStuck.outerHeight();
-    }
-
-    if ($startsEnvironment.length) {
-      topPostion += $startsEnvironment.outerHeight() + prependTopDefault;
-    }
-
-    if ($erased.length) {
-      topPostion += $erased.outerHeight() + prependTopDefault;
-    }
-
-    if ($flashError.length) {
-      topPostion += $flashError.outerHeight();
-    }
-
-    this.$buildTrace.css({
-      top: topPostion,
-    });
-  };
-
   Build.prototype.initSidebar = function () {
     this.$sidebar = $('.js-build-sidebar');
     this.$sidebar.niceScroll();
@@ -196,10 +173,13 @@ window.Build = (function () {
     })
       .done((log) => {
         gl.utils.setCiStatusFavicon(`${this.pageUrl}/status.json`);
+
         if (log.state) {
           this.state = log.state;
         }
 
+        this.windowSize = this.$buildTraceOutput.prop('scrollHeight');
+
         if (log.append) {
           this.$buildTraceOutput.append(log.html);
           this.logBytes += log.size;
@@ -220,16 +200,14 @@ window.Build = (function () {
         }
 
         if (!log.complete) {
-          this.toggleScrollAnimation(true);
+          if (!this.hasBeenScrolled) {
+            this.toggleScrollAnimation(true);
+          } else {
+            this.toggleScrollAnimation(false);
+          }
 
           Build.timeout = setTimeout(() => {
-            //eslint-disable-next-line
-            this.getBuildTrace()
-              .then(() => {
-                if (!this.hasBeenScrolled) {
-                  this.scrollToBottom();
-                }
-              });
+            this.getBuildTrace();
           }, 4000);
         } else {
           this.$buildRefreshAnimation.remove();
@@ -242,7 +220,13 @@ window.Build = (function () {
       })
       .fail(() => {
         this.$buildRefreshAnimation.remove();
-      });
+      })
+      .then(() => {
+        if (!this.hasBeenScrolled) {
+          this.scrollDown();
+        }
+      })
+      .then(() => this.toggleScroll());
   };
 
   Build.prototype.shouldHideSidebarForViewport = function () {
@@ -254,14 +238,11 @@ window.Build = (function () {
     const shouldShow = typeof shouldHide === 'boolean' ? !shouldHide : undefined;
     const $toggleButton = $('.js-sidebar-build-toggle-header');
 
-    this.$buildTrace
-      .toggleClass('sidebar-expanded', shouldShow)
-      .toggleClass('sidebar-collapsed', shouldHide);
     this.$sidebar
       .toggleClass('right-sidebar-expanded', shouldShow)
       .toggleClass('right-sidebar-collapsed', shouldHide);
 
-    $('.js-build-page')
+    this.$topBar
       .toggleClass('sidebar-expanded', shouldShow)
       .toggleClass('sidebar-collapsed', shouldHide);
 
@@ -274,17 +255,10 @@ window.Build = (function () {
 
   Build.prototype.sidebarOnResize = function () {
     this.toggleSidebar(this.shouldHideSidebarForViewport());
-
-    this.verifyTopPosition();
-
-    if (this.canScroll()) {
-      this.toggleScroll();
-    }
   };
 
   Build.prototype.sidebarOnClick = function () {
     if (this.shouldHideSidebarForViewport()) this.toggleSidebar();
-    this.verifyTopPosition();
   };
 
   Build.prototype.updateArtifactRemoveDate = function () {
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js
index 86d99dd87da9eaba47297dfb753e09c0bcb0572c..2c38440a2afcbd93e2e7af035bc11336b32e13d6 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js
+++ b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js
@@ -1,29 +1,30 @@
-/* eslint-disable no-param-reassign */
-
 import Vue from 'vue';
-import VueResource from 'vue-resource';
-import CommitPipelinesTable from './pipelines_table';
-
-Vue.use(VueResource);
+import commitPipelinesTable from './pipelines_table.vue';
 
 /**
- * Commits View > Pipelines Tab > Pipelines Table.
- *
- * Renders Pipelines table in pipelines tab in the commits show view.
+ * Used in:
+ *  - Commit details View > Pipelines Tab > Pipelines Table.
+ *  - Merge Request details View > Pipelines Tab > Pipelines Table.
+ *  - New Merge Request View > Pipelines Tab > Pipelines Table.
  */
 
-// export for use in merge_request_tabs.js (TODO: remove this hack)
+const CommitPipelinesTable = Vue.extend(commitPipelinesTable);
+
+// export for use in merge_request_tabs.js (TODO: remove this hack when we understand how to load
+// vue.js in merge_request_tabs.js)
 window.gl = window.gl || {};
 window.gl.CommitPipelinesTable = CommitPipelinesTable;
 
-$(() => {
-  gl.commits = gl.commits || {};
-  gl.commits.pipelines = gl.commits.pipelines || {};
-
+document.addEventListener('DOMContentLoaded', () => {
   const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view');
 
   if (pipelineTableViewEl && pipelineTableViewEl.dataset.disableInitialization === undefined) {
-    gl.commits.pipelines.PipelinesTableBundle = new CommitPipelinesTable().$mount();
-    pipelineTableViewEl.appendChild(gl.commits.pipelines.PipelinesTableBundle.$el);
+    const table = new CommitPipelinesTable({
+      propsData: {
+        endpoint: pipelineTableViewEl.dataset.endpoint,
+        helpPagePath: pipelineTableViewEl.dataset.helpPagePath,
+      },
+    }).$mount();
+    pipelineTableViewEl.appendChild(table.$el);
   }
 });
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.js b/app/assets/javascripts/commit/pipelines/pipelines_table.js
deleted file mode 100644
index 70ba83ce5b9fb201e2131c04bbc3c6308462b9aa..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/commit/pipelines/pipelines_table.js
+++ /dev/null
@@ -1,191 +0,0 @@
-import Vue from 'vue';
-import Visibility from 'visibilityjs';
-import pipelinesTableComponent from '../../vue_shared/components/pipelines_table.vue';
-import PipelinesService from '../../pipelines/services/pipelines_service';
-import PipelineStore from '../../pipelines/stores/pipelines_store';
-import eventHub from '../../pipelines/event_hub';
-import emptyState from '../../pipelines/components/empty_state.vue';
-import errorState from '../../pipelines/components/error_state.vue';
-import loadingIcon from '../../vue_shared/components/loading_icon.vue';
-import '../../lib/utils/common_utils';
-import '../../vue_shared/vue_resource_interceptor';
-import Poll from '../../lib/utils/poll';
-
-/**
- *
- * Uses `pipelines-table-component` to render Pipelines table with an API call.
- * Endpoint is provided in HTML and passed as `endpoint`.
- * We need a store to store the received environemnts.
- * We need a service to communicate with the server.
- *
- */
-
-export default Vue.component('pipelines-table', {
-
-  components: {
-    pipelinesTableComponent,
-    errorState,
-    emptyState,
-    loadingIcon,
-  },
-
-  /**
-   * Accesses the DOM to provide the needed data.
-   * Returns the necessary props to render `pipelines-table-component` component.
-   *
-   * @return {Object}
-   */
-  data() {
-    const store = new PipelineStore();
-
-    return {
-      endpoint: null,
-      helpPagePath: null,
-      store,
-      state: store.state,
-      isLoading: false,
-      hasError: false,
-      isMakingRequest: false,
-      updateGraphDropdown: false,
-      hasMadeRequest: false,
-    };
-  },
-
-  computed: {
-    shouldRenderErrorState() {
-      return this.hasError && !this.isLoading;
-    },
-
-    /**
-     * Empty state is only rendered if after the first request we receive no pipelines.
-     *
-     * @return {Boolean}
-     */
-    shouldRenderEmptyState() {
-      return !this.state.pipelines.length &&
-        !this.isLoading &&
-        this.hasMadeRequest &&
-        !this.hasError;
-    },
-
-    shouldRenderTable() {
-      return !this.isLoading &&
-        this.state.pipelines.length > 0 &&
-        !this.hasError;
-    },
-  },
-
-  /**
-   * When the component is about to be mounted, tell the service to fetch the data
-   *
-   * A request to fetch the pipelines will be made.
-   * In case of a successfull response we will store the data in the provided
-   * store, in case of a failed response we need to warn the user.
-   *
-   */
-  beforeMount() {
-    const element = document.querySelector('#commit-pipeline-table-view');
-
-    this.endpoint = element.dataset.endpoint;
-    this.helpPagePath = element.dataset.helpPagePath;
-    this.service = new PipelinesService(this.endpoint);
-
-    this.poll = new Poll({
-      resource: this.service,
-      method: 'getPipelines',
-      successCallback: this.successCallback,
-      errorCallback: this.errorCallback,
-      notificationCallback: this.setIsMakingRequest,
-    });
-
-    if (!Visibility.hidden()) {
-      this.isLoading = true;
-      this.poll.makeRequest();
-    } else {
-      // If tab is not visible we need to make the first request so we don't show the empty
-      // state without knowing if there are any pipelines
-      this.fetchPipelines();
-    }
-
-    Visibility.change(() => {
-      if (!Visibility.hidden()) {
-        this.poll.restart();
-      } else {
-        this.poll.stop();
-      }
-    });
-
-    eventHub.$on('refreshPipelines', this.fetchPipelines);
-  },
-
-  beforeDestroy() {
-    eventHub.$off('refreshPipelines');
-  },
-
-  destroyed() {
-    this.poll.stop();
-  },
-
-  methods: {
-    fetchPipelines() {
-      this.isLoading = true;
-
-      return this.service.getPipelines()
-        .then(response => this.successCallback(response))
-        .catch(() => this.errorCallback());
-    },
-
-    successCallback(resp) {
-      const response = resp.json();
-
-      this.hasMadeRequest = true;
-
-      // depending of the endpoint the response can either bring a `pipelines` key or not.
-      const pipelines = response.pipelines || response;
-      this.store.storePipelines(pipelines);
-      this.isLoading = false;
-      this.updateGraphDropdown = true;
-    },
-
-    errorCallback() {
-      this.hasError = true;
-      this.isLoading = false;
-      this.updateGraphDropdown = false;
-    },
-
-    setIsMakingRequest(isMakingRequest) {
-      this.isMakingRequest = isMakingRequest;
-
-      if (isMakingRequest) {
-        this.updateGraphDropdown = false;
-      }
-    },
-  },
-
-  template: `
-    <div class="content-list pipelines">
-
-      <loading-icon
-        label="Loading pipelines"
-        size="3"
-        v-if="isLoading"
-        />
-
-      <empty-state
-        v-if="shouldRenderEmptyState"
-        :help-page-path="helpPagePath" />
-
-      <error-state v-if="shouldRenderErrorState" />
-
-      <div
-        class="table-holder"
-        v-if="shouldRenderTable">
-        <pipelines-table-component
-          :pipelines="state.pipelines"
-          :service="service"
-          :update-graph-dropdown="updateGraphDropdown"
-          />
-      </div>
-    </div>
-  `,
-});
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.vue b/app/assets/javascripts/commit/pipelines/pipelines_table.vue
new file mode 100644
index 0000000000000000000000000000000000000000..3c77f14d533c2eb2ef8915aabbfa2d713fd73744
--- /dev/null
+++ b/app/assets/javascripts/commit/pipelines/pipelines_table.vue
@@ -0,0 +1,90 @@
+<script>
+  import PipelinesService from '../../pipelines/services/pipelines_service';
+  import PipelineStore from '../../pipelines/stores/pipelines_store';
+  import pipelinesMixin from '../../pipelines/mixins/pipelines';
+
+  export default {
+    props: {
+      endpoint: {
+        type: String,
+        required: true,
+      },
+      helpPagePath: {
+        type: String,
+        required: true,
+      },
+    },
+    mixins: [
+      pipelinesMixin,
+    ],
+
+    data() {
+      const store = new PipelineStore();
+
+      return {
+        store,
+        state: store.state,
+      };
+    },
+
+    computed: {
+      /**
+       * Empty state is only rendered if after the first request we receive no pipelines.
+       *
+       * @return {Boolean}
+       */
+      shouldRenderEmptyState() {
+        return !this.state.pipelines.length &&
+          !this.isLoading &&
+          this.hasMadeRequest &&
+          !this.hasError;
+      },
+
+      shouldRenderTable() {
+        return !this.isLoading &&
+          this.state.pipelines.length > 0 &&
+          !this.hasError;
+      },
+    },
+    created() {
+      this.service = new PipelinesService(this.endpoint);
+    },
+    methods: {
+      successCallback(resp) {
+        const response = resp.json();
+
+        // depending of the endpoint the response can either bring a `pipelines` key or not.
+        const pipelines = response.pipelines || response;
+        this.setCommonData(pipelines);
+      },
+    },
+  };
+</script>
+<template>
+  <div class="content-list pipelines">
+
+    <loading-icon
+      label="Loading pipelines"
+      size="3"
+      v-if="isLoading"
+      />
+
+    <empty-state
+      v-if="shouldRenderEmptyState"
+      :help-page-path="helpPagePath"
+      />
+
+    <error-state
+      v-if="shouldRenderErrorState"
+      />
+
+    <div
+      class="table-holder"
+      v-if="shouldRenderTable">
+      <pipelines-table-component
+        :pipelines="state.pipelines"
+        :update-graph-dropdown="updateGraphDropdown"
+        />
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js
index 725ec7b9c70aa089ff3090958674625ea6fefa18..1be9df19c816827230e951b57a0afa1211595db7 100644
--- a/app/assets/javascripts/diff.js
+++ b/app/assets/javascripts/diff.js
@@ -1,6 +1,7 @@
 /* eslint-disable class-methods-use-this */
 
 import './lib/utils/url_utility';
+import FilesCommentButton from './files_comment_button';
 
 const UNFOLD_COUNT = 20;
 let isBound = false;
@@ -8,8 +9,10 @@ let isBound = false;
 class Diff {
   constructor() {
     const $diffFile = $('.files .diff-file');
+
     $diffFile.singleFileDiff();
-    $diffFile.filesCommentButton();
+
+    FilesCommentButton.init($diffFile);
 
     $diffFile.each((index, file) => new gl.ImageFile(file));
 
diff --git a/app/assets/javascripts/diff_notes/components/diff_note_avatars.js b/app/assets/javascripts/diff_notes/components/diff_note_avatars.js
index 517bdb6be092a9acdc20dfe8878d7877748ca490..c37249c060a69bf1b51b8d15db2cb57fdcd4840f 100644
--- a/app/assets/javascripts/diff_notes/components/diff_note_avatars.js
+++ b/app/assets/javascripts/diff_notes/components/diff_note_avatars.js
@@ -139,9 +139,9 @@ const DiffNoteAvatars = Vue.extend({
       const notesCount = this.notesCount;
 
       $(this.$el).closest('.js-avatar-container')
-        .toggleClass('js-no-comment-btn', notesCount > 0)
+        .toggleClass('no-comment-btn', notesCount > 0)
         .nextUntil('.js-avatar-container')
-        .toggleClass('js-no-comment-btn', notesCount > 0);
+        .toggleClass('no-comment-btn', notesCount > 0);
     },
     toggleDiscussionsToggleState() {
       const $notesHolders = $(this.$el).closest('.code').find('.notes_holder');
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 5f87a05067bf11756addd428aba2040cff5cd227..4247540de22771e545d75ad27df80ec3dd3477ab 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -55,6 +55,7 @@ import RefSelectDropdown from './ref_select_dropdown';
 import GfmAutoComplete from './gfm_auto_complete';
 import ShortcutsBlob from './shortcuts_blob';
 import initSettingsPanels from './settings_panels';
+import initExperimentalFlags from './experimental_flags';
 
 (function() {
   var Dispatcher;
@@ -79,7 +80,18 @@ import initSettingsPanels from './settings_panels';
       path = page.split(':');
       shortcut_handler = null;
 
-      new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources).setup();
+      $('.js-gfm-input').each((i, el) => {
+        const gfm = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources);
+        const enableGFM = gl.utils.convertPermissionToBoolean(el.dataset.supportsAutocomplete);
+        gfm.setup($(el), {
+          emojis: true,
+          members: enableGFM,
+          issues: enableGFM,
+          milestones: enableGFM,
+          mergeRequests: enableGFM,
+          labels: enableGFM,
+        });
+      });
 
       function initBlob() {
         new LineHighlighter();
@@ -109,6 +121,9 @@ import initSettingsPanels from './settings_panels';
       }
 
       switch (page) {
+        case 'profiles:preferences:show':
+          initExperimentalFlags();
+          break;
         case 'sessions:new':
           new UsernameValidator();
           new ActiveTabMemoizer();
@@ -176,7 +191,7 @@ import initSettingsPanels from './settings_panels';
         case 'groups:milestones:update':
           new ZenMode();
           new gl.DueDateSelectors();
-          new gl.GLForm($('.milestone-form'));
+          new gl.GLForm($('.milestone-form'), true);
           break;
         case 'projects:compare:show':
           new gl.Diff();
@@ -188,18 +203,18 @@ import initSettingsPanels from './settings_panels';
         case 'projects:issues:new':
         case 'projects:issues:edit':
           shortcut_handler = new ShortcutsNavigation();
-          new gl.GLForm($('.issue-form'));
+          new gl.GLForm($('.issue-form'), true);
           new IssuableForm($('.issue-form'));
           new LabelsSelect();
           new MilestoneSelect();
           new gl.IssuableTemplateSelectors();
           break;
-        case 'projects:merge_requests:new':
-        case 'projects:merge_requests:new_diffs':
+        case 'projects:merge_requests:creations:new':
+        case 'projects:merge_requests:creations:diffs':
         case 'projects:merge_requests:edit':
           new gl.Diff();
           shortcut_handler = new ShortcutsNavigation();
-          new gl.GLForm($('.merge-request-form'));
+          new gl.GLForm($('.merge-request-form'), true);
           new IssuableForm($('.merge-request-form'));
           new LabelsSelect();
           new MilestoneSelect();
@@ -208,32 +223,30 @@ import initSettingsPanels from './settings_panels';
           break;
         case 'projects:tags:new':
           new ZenMode();
-          new gl.GLForm($('.tag-form'));
+          new gl.GLForm($('.tag-form'), true);
           new RefSelectDropdown($('.js-branch-select'), window.gl.availableRefs);
           break;
         case 'projects:snippets:new':
         case 'projects:snippets:edit':
         case 'projects:snippets:create':
         case 'projects:snippets:update':
+          new gl.GLForm($('.snippet-form'), true);
+          break;
         case 'snippets:new':
         case 'snippets:edit':
         case 'snippets:create':
         case 'snippets:update':
-          new gl.GLForm($('.snippet-form'));
+          new gl.GLForm($('.snippet-form'), false);
           break;
         case 'projects:releases:edit':
           new ZenMode();
-          new gl.GLForm($('.release-form'));
+          new gl.GLForm($('.release-form'), true);
           break;
         case 'projects:merge_requests:show':
           new gl.Diff();
           shortcut_handler = new ShortcutsIssuable(true);
           new ZenMode();
           break;
-        case "projects:merge_requests:diffs":
-          new gl.Diff();
-          new ZenMode();
-          break;
         case 'dashboard:activity':
           new gl.Activities();
           break;
@@ -302,7 +315,7 @@ import initSettingsPanels from './settings_panels';
           new gl.Members();
           new UsersSelect();
           break;
-        case 'projects:members:show':
+        case 'projects:settings:members:show':
           new gl.MemberExpirationDate('.js-access-expiration-date-groups');
           new GroupsSelect();
           new gl.MemberExpirationDate();
@@ -369,7 +382,7 @@ import initSettingsPanels from './settings_panels';
         case 'search:show':
           new Search();
           break;
-        case 'projects:repository:show':
+        case 'projects:settings:repository:show':
           // Initialize Protected Branch Settings
           new gl.ProtectedBranchCreate();
           new gl.ProtectedBranchEditList();
@@ -379,7 +392,7 @@ import initSettingsPanels from './settings_panels';
           // Initialize expandable settings panels
           initSettingsPanels();
           break;
-        case 'projects:ci_cd:show':
+        case 'projects:settings:ci_cd:show':
           new gl.ProjectVariables();
           break;
         case 'ci:lints:create':
@@ -471,7 +484,7 @@ import initSettingsPanels from './settings_panels';
               new gl.Wikis();
               shortcut_handler = new ShortcutsWiki();
               new ZenMode();
-              new gl.GLForm($('.wiki-form'));
+              new gl.GLForm($('.wiki-form'), true);
               break;
             case 'snippets':
               shortcut_handler = new ShortcutsNavigation();
diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js
index 98ddcc20036c6086fc1713f8d40c0dcf6df73f8d..73675d300be97776af7cf43084aba1cfdd20f4d0 100644
--- a/app/assets/javascripts/dropzone_input.js
+++ b/app/assets/javascripts/dropzone_input.js
@@ -287,6 +287,10 @@ window.DropzoneInput = (function() {
       $uploadingErrorMessage.html(message);
     };
 
+    closeAlertMessage = function() {
+      return form.find('.div-dropzone-alert').alert('close');
+    };
+
     form.find('.markdown-selector').click(function(e) {
       e.preventDefault();
       $(this).closest('.gfm-form').find('.div-dropzone').click();
diff --git a/app/assets/javascripts/emoji/index.js b/app/assets/javascripts/emoji/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..cac35d6eed5e2569b6f94cf69f4c7754b30dfebf
--- /dev/null
+++ b/app/assets/javascripts/emoji/index.js
@@ -0,0 +1,99 @@
+import emojiMap from 'emojis/digests.json';
+import emojiAliases from 'emojis/aliases.json';
+
+export const validEmojiNames = [...Object.keys(emojiMap), ...Object.keys(emojiAliases)];
+
+export function normalizeEmojiName(name) {
+  return Object.prototype.hasOwnProperty.call(emojiAliases, name) ? emojiAliases[name] : name;
+}
+
+export function isEmojiNameValid(name) {
+  return validEmojiNames.indexOf(name) >= 0;
+}
+
+export function filterEmojiNames(filter) {
+  const match = filter.toLowerCase();
+  return validEmojiNames.filter(name => name.indexOf(match) >= 0);
+}
+
+export function filterEmojiNamesByAlias(filter) {
+  return _.uniq(filterEmojiNames(filter).map(name => normalizeEmojiName(name)));
+}
+
+let emojiCategoryMap;
+export function getEmojiCategoryMap() {
+  if (!emojiCategoryMap) {
+    emojiCategoryMap = {
+      activity: [],
+      people: [],
+      nature: [],
+      food: [],
+      travel: [],
+      objects: [],
+      symbols: [],
+      flags: [],
+    };
+    Object.keys(emojiMap).forEach((name) => {
+      const emoji = emojiMap[name];
+      if (emojiCategoryMap[emoji.category]) {
+        emojiCategoryMap[emoji.category].push(name);
+      }
+    });
+  }
+  return emojiCategoryMap;
+}
+
+export function getEmojiInfo(query) {
+  let name = normalizeEmojiName(query);
+  let emojiInfo = emojiMap[name];
+
+  // Fallback to question mark for unknown emojis
+  if (!emojiInfo) {
+    name = 'grey_question';
+    emojiInfo = emojiMap[name];
+  }
+
+  return { ...emojiInfo, name };
+}
+
+export function emojiFallbackImageSrc(inputName) {
+  const { name, digest } = getEmojiInfo(inputName);
+  return `${gon.asset_host || ''}${gon.relative_url_root || ''}/assets/emoji/${name}-${digest}.png`;
+}
+
+export function emojiImageTag(name, src) {
+  return `<img class="emoji" title=":${name}:" alt=":${name}:" src="${src}" width="20" height="20" align="absmiddle" />`;
+}
+
+export function glEmojiTag(inputName, options) {
+  const opts = { sprite: false, forceFallback: false, ...options };
+  const { name, ...emojiInfo } = getEmojiInfo(inputName);
+
+  const fallbackImageSrc = emojiFallbackImageSrc(name);
+  const fallbackSpriteClass = `emoji-${name}`;
+
+  const classList = [];
+  if (opts.forceFallback && opts.sprite) {
+    classList.push('emoji-icon');
+    classList.push(fallbackSpriteClass);
+  }
+  const classAttribute = classList.length > 0 ? `class="${classList.join(' ')}"` : '';
+  const fallbackSpriteAttribute = opts.sprite ? `data-fallback-sprite-class="${fallbackSpriteClass}"` : '';
+  let contents = emojiInfo.moji;
+  if (opts.forceFallback && !opts.sprite) {
+    contents = emojiImageTag(name, fallbackImageSrc);
+  }
+
+  return `
+    <gl-emoji
+      ${classAttribute}
+      data-name="${name}"
+      data-fallback-src="${fallbackImageSrc}"
+      ${fallbackSpriteAttribute}
+      data-unicode-version="${emojiInfo.unicodeVersion}"
+      title="${emojiInfo.description}"
+    >
+      ${contents}
+    </gl-emoji>
+  `;
+}
diff --git a/app/assets/javascripts/emoji/support/index.js b/app/assets/javascripts/emoji/support/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..1f7852dd487dc02b585589038bcbb009046dbcbc
--- /dev/null
+++ b/app/assets/javascripts/emoji/support/index.js
@@ -0,0 +1,10 @@
+import isEmojiUnicodeSupported from './is_emoji_unicode_supported';
+import getUnicodeSupportMap from './unicode_support_map';
+
+// cache browser support map between calls
+let browserUnicodeSupportMap;
+
+export default function isEmojiUnicodeSupportedByBrowser(emojiUnicode, unicodeVersion) {
+  browserUnicodeSupportMap = browserUnicodeSupportMap || getUnicodeSupportMap();
+  return isEmojiUnicodeSupported(browserUnicodeSupportMap, emojiUnicode, unicodeVersion);
+}
diff --git a/app/assets/javascripts/behaviors/gl_emoji/is_emoji_unicode_supported.js b/app/assets/javascripts/emoji/support/is_emoji_unicode_supported.js
similarity index 99%
rename from app/assets/javascripts/behaviors/gl_emoji/is_emoji_unicode_supported.js
rename to app/assets/javascripts/emoji/support/is_emoji_unicode_supported.js
index 4f8884d05ac409bb68dbc71a9c8292fb6ae4bacf..3fd23efa9f83f39a22cad6a515be13b083f1cd8c 100644
--- a/app/assets/javascripts/behaviors/gl_emoji/is_emoji_unicode_supported.js
+++ b/app/assets/javascripts/emoji/support/is_emoji_unicode_supported.js
@@ -111,7 +111,7 @@ function isEmojiUnicodeSupported(unicodeSupportMap = {}, emojiUnicode, unicodeVe
 }
 
 export {
-  isEmojiUnicodeSupported,
+  isEmojiUnicodeSupported as default,
   isFlagEmoji,
   isKeycapEmoji,
   isSkinToneComboEmoji,
diff --git a/app/assets/javascripts/behaviors/gl_emoji/unicode_support_map.js b/app/assets/javascripts/emoji/support/unicode_support_map.js
similarity index 98%
rename from app/assets/javascripts/behaviors/gl_emoji/unicode_support_map.js
rename to app/assets/javascripts/emoji/support/unicode_support_map.js
index 257df55e54fb44fb2d51ed36934dacae864f3088..755381c2f9569d74a37cb2a00dfe6a63a28d177b 100644
--- a/app/assets/javascripts/behaviors/gl_emoji/unicode_support_map.js
+++ b/app/assets/javascripts/emoji/support/unicode_support_map.js
@@ -140,7 +140,7 @@ function generateUnicodeSupportMap(testMap) {
   return resultMap;
 }
 
-function getUnicodeSupportMap() {
+export default function getUnicodeSupportMap() {
   let unicodeSupportMap;
   let userAgentFromCache;
 
@@ -165,8 +165,3 @@ function getUnicodeSupportMap() {
 
   return unicodeSupportMap;
 }
-
-export {
-  getUnicodeSupportMap,
-  generateUnicodeSupportMap,
-};
diff --git a/app/assets/javascripts/environments/components/environment.vue b/app/assets/javascripts/environments/components/environment.vue
index 8120ef182d4299f6b5d51572245307b91a7d8563..91ed8c8467f68f52e34d9ec3863aa5fcdf6b412c 100644
--- a/app/assets/javascripts/environments/components/environment.vue
+++ b/app/assets/javascripts/environments/components/environment.vue
@@ -32,7 +32,6 @@ export default {
       state: store.state,
       visibility: 'available',
       isLoading: false,
-      isLoadingFolderContent: false,
       cssContainerClass: environmentsData.cssClass,
       endpoint: environmentsData.environmentsDataEndpoint,
       canCreateDeployment: environmentsData.canCreateDeployment,
@@ -86,9 +85,6 @@ export default {
       errorCallback: this.errorCallback,
       notificationCallback: (isMakingRequest) => {
         this.isMakingRequest = isMakingRequest;
-
-        // We need to verify if any folder is open to also fecth it
-        this.openFolders = this.store.getOpenFolders();
       },
     });
 
@@ -119,7 +115,7 @@ export default {
       this.store.toggleFolder(folder);
 
       if (!folder.isOpen) {
-        this.fetchChildEnvironments(folder, folderUrl);
+        this.fetchChildEnvironments(folder, folderUrl, true);
       }
     },
 
@@ -147,19 +143,17 @@ export default {
         .catch(this.errorCallback);
     },
 
-    fetchChildEnvironments(folder, folderUrl) {
-      this.isLoadingFolderContent = true;
+    fetchChildEnvironments(folder, folderUrl, showLoader = false) {
+      this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', showLoader);
 
       this.service.getFolderContent(folderUrl)
         .then(resp => resp.json())
-        .then((response) => {
-          this.store.setfolderContent(folder, response.environments);
-          this.isLoadingFolderContent = false;
-        })
+        .then(response => this.store.setfolderContent(folder, response.environments))
+        .then(() => this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false))
         .catch(() => {
-          this.isLoadingFolderContent = false;
           // eslint-disable-next-line no-new
           new Flash('An error occurred while fetching the environments.');
+          this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false);
         });
     },
 
@@ -176,13 +170,13 @@ export default {
     successCallback(resp) {
       this.saveData(resp);
 
-      // If folders are open while polling we need to open them again
-      if (this.openFolders.length) {
-        this.openFolders.map((folder) => {
+      // We need to verify if any folder is open to also update it
+      const openFolders = this.store.getOpenFolders();
+      if (openFolders.length) {
+        openFolders.forEach((folder) => {
           // TODO - Move this to the backend
           const folderUrl = `${window.location.pathname}/folders/${folder.folderName}`;
 
-          this.store.updateFolder(folder, 'isOpen', true);
           return this.fetchChildEnvironments(folder, folderUrl);
         });
       }
@@ -267,7 +261,7 @@ export default {
           :environments="state.environments"
           :can-create-deployment="canCreateDeploymentParsed"
           :can-read-environment="canReadEnvironmentParsed"
-          :is-loading-folder-content="isLoadingFolderContent" />
+          />
       </div>
 
       <table-pagination
diff --git a/app/assets/javascripts/environments/components/environment_actions.vue b/app/assets/javascripts/environments/components/environment_actions.vue
index a2448520a5f6695b24944cd40466f98a776c7159..e7495677e7c948286dc4b5d5101127019fba2694 100644
--- a/app/assets/javascripts/environments/components/environment_actions.vue
+++ b/app/assets/javascripts/environments/components/environment_actions.vue
@@ -2,6 +2,7 @@
 import playIconSvg from 'icons/_icon_play.svg';
 import eventHub from '../event_hub';
 import loadingIcon from '../../vue_shared/components/loading_icon.vue';
+import tooltip from '../../vue_shared/directives/tooltip';
 
 export default {
   props: {
@@ -12,6 +13,10 @@ export default {
     },
   },
 
+  directives: {
+    tooltip,
+  },
+
   components: {
     loadingIcon,
   },
@@ -33,8 +38,6 @@ export default {
     onClickAction(endpoint) {
       this.isLoading = true;
 
-      $(this.$refs.tooltip).tooltip('destroy');
-
       eventHub.$emit('postAction', endpoint);
     },
 
@@ -53,11 +56,11 @@ export default {
     class="btn-group"
     role="group">
     <button
+      v-tooltip
       type="button"
-      class="dropdown btn btn-default dropdown-new js-dropdown-play-icon-container has-tooltip"
+      class="dropdown btn btn-default dropdown-new js-dropdown-play-icon-container"
       data-container="body"
       data-toggle="dropdown"
-      ref="tooltip"
       :title="title"
       :aria-label="title"
       :disabled="isLoading">
diff --git a/app/assets/javascripts/environments/components/environment_external_url.vue b/app/assets/javascripts/environments/components/environment_external_url.vue
index eaeec2bc53c60f043b193f15853345fa159a8fbe..6b749814ea42a1134b4782c0c79894a77f5d4dda 100644
--- a/app/assets/javascripts/environments/components/environment_external_url.vue
+++ b/app/assets/javascripts/environments/components/environment_external_url.vue
@@ -1,4 +1,6 @@
 <script>
+import tooltip from '../../vue_shared/directives/tooltip';
+
 /**
  * Renders the external url link in environments table.
  */
@@ -10,6 +12,10 @@ export default {
     },
   },
 
+  directives: {
+    tooltip,
+  },
+
   computed: {
     title() {
       return 'Open';
@@ -19,7 +25,8 @@ export default {
 </script>
 <template>
   <a
-    class="btn external-url has-tooltip"
+    v-tooltip
+    class="btn external-url"
     data-container="body"
     target="_blank"
     rel="noopener noreferrer nofollow"
diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue
index 809c147bf25be7c0b9190b4c53c68f4176a7b4e2..b25113e0fc672dbbf98b85e4219239155fb21cc6 100644
--- a/app/assets/javascripts/environments/components/environment_item.vue
+++ b/app/assets/javascripts/environments/components/environment_item.vue
@@ -403,6 +403,14 @@ export default {
       return '';
     },
 
+    displayEnvironmentActions() {
+      return this.hasManualActions ||
+             this.externalURL ||
+             this.monitoringUrl ||
+             this.hasStopAction ||
+             this.canRetry;
+    },
+
     /**
      * Constructs folder URL based on the current location and the folder id.
      *
@@ -535,9 +543,12 @@ export default {
       </span>
     </div>
 
-    <div class="table-section section-30 table-button-footer" role="gridcell">
+    <div
+      v-if="!model.isFolder && displayEnvironmentActions"
+      class="table-section section-30 table-button-footer"
+      role="gridcell">
+
       <div
-        v-if="!model.isFolder"
         class="btn-group table-action-buttons"
         role="group">
 
diff --git a/app/assets/javascripts/environments/components/environment_monitoring.vue b/app/assets/javascripts/environments/components/environment_monitoring.vue
index 07cf92281a0f9649c9990e2ba0e281f0267b9d69..1655561cdd31b8394134c3d74c240345b387e08e 100644
--- a/app/assets/javascripts/environments/components/environment_monitoring.vue
+++ b/app/assets/javascripts/environments/components/environment_monitoring.vue
@@ -2,6 +2,8 @@
 /**
  * Renders the Monitoring (Metrics) link in environments table.
  */
+import tooltip from '../../vue_shared/directives/tooltip';
+
 export default {
   props: {
     monitoringUrl: {
@@ -10,6 +12,10 @@ export default {
     },
   },
 
+  directives: {
+    tooltip,
+  },
+
   computed: {
     title() {
       return 'Monitoring';
@@ -19,7 +25,8 @@ export default {
 </script>
 <template>
   <a
-    class="btn monitoring-url has-tooltip hidden-xs hidden-sm"
+    v-tooltip
+    class="btn monitoring-url hidden-xs hidden-sm"
     data-container="body"
     rel="noopener noreferrer nofollow"
     :href="monitoringUrl"
diff --git a/app/assets/javascripts/environments/components/environment_stop.vue b/app/assets/javascripts/environments/components/environment_stop.vue
index 091c543860b081b0033744694296caaac36cfca6..85f11d2071b9fd2f0558b39c8f6aaefb2577bf89 100644
--- a/app/assets/javascripts/environments/components/environment_stop.vue
+++ b/app/assets/javascripts/environments/components/environment_stop.vue
@@ -5,6 +5,7 @@
  */
 import eventHub from '../event_hub';
 import loadingIcon from '../../vue_shared/components/loading_icon.vue';
+import tooltip from '../../vue_shared/directives/tooltip';
 
 export default {
   props: {
@@ -14,6 +15,10 @@ export default {
     },
   },
 
+  directives: {
+    tooltip,
+  },
+
   data() {
     return {
       isLoading: false,
@@ -46,8 +51,9 @@ export default {
 </script>
 <template>
   <button
+    v-tooltip
     type="button"
-    class="btn stop-env-link has-tooltip hidden-xs hidden-sm"
+    class="btn stop-env-link hidden-xs hidden-sm"
     data-container="body"
     @click="onClick"
     :disabled="isLoading"
diff --git a/app/assets/javascripts/environments/components/environment_terminal_button.vue b/app/assets/javascripts/environments/components/environment_terminal_button.vue
index 1ca65a799515f158a50bb92118ed846cac9e7c8a..2037bf618e38acf6676e745b2cee9da1c0143a8e 100644
--- a/app/assets/javascripts/environments/components/environment_terminal_button.vue
+++ b/app/assets/javascripts/environments/components/environment_terminal_button.vue
@@ -4,6 +4,7 @@
  * Used in environments table.
  */
 import terminalIconSvg from 'icons/_icon_terminal.svg';
+import tooltip from '../../vue_shared/directives/tooltip';
 
 export default {
   props: {
@@ -14,6 +15,10 @@ export default {
     },
   },
 
+  directives: {
+    tooltip,
+  },
+
   data() {
     return {
       terminalIconSvg,
@@ -29,7 +34,8 @@ export default {
 </script>
 <template>
   <a
-    class="btn terminal-button has-tooltip hidden-xs hidden-sm"
+    v-tooltip
+    class="btn terminal-button hidden-xs hidden-sm"
     data-container="body"
     :title="title"
     :aria-label="title"
diff --git a/app/assets/javascripts/environments/components/environments_table.vue b/app/assets/javascripts/environments/components/environments_table.vue
index b1fd9db650b1dbcb842947121c1a0cdb3f7266ea..175cc8f1f72d8239f2e26a79f98744d416914d58 100644
--- a/app/assets/javascripts/environments/components/environments_table.vue
+++ b/app/assets/javascripts/environments/components/environments_table.vue
@@ -29,12 +29,6 @@ export default {
       required: false,
       default: false,
     },
-
-    isLoadingFolderContent: {
-      type: Boolean,
-      required: false,
-      default: false,
-    },
   },
 
   methods: {
@@ -74,7 +68,7 @@ export default {
         />
 
       <template v-if="model.isFolder && model.isOpen && model.children && model.children.length > 0">
-        <div v-if="isLoadingFolderContent">
+        <div v-if="model.isLoadingFolderContent">
           <loading-icon size="2" />
         </div>
 
diff --git a/app/assets/javascripts/environments/stores/environments_store.js b/app/assets/javascripts/environments/stores/environments_store.js
index a5773dd7e4f7908ae0068ef224145e19ff112c72..038c149be2dc15f7c26d542bb840b0edfd897e51 100644
--- a/app/assets/javascripts/environments/stores/environments_store.js
+++ b/app/assets/javascripts/environments/stores/environments_store.js
@@ -35,14 +35,18 @@ export default class EnvironmentsStore {
    */
   storeEnvironments(environments = []) {
     const filteredEnvironments = environments.map((env) => {
+      const oldEnvironmentState = this.state.environments
+        .find(element => element.id === env.latest.id) || {};
+
       let filtered = {};
 
       if (env.size > 1) {
         filtered = Object.assign({}, env, {
           isFolder: true,
+          isLoadingFolderContent: oldEnvironmentState.isLoading || false,
           folderName: env.name,
-          isOpen: false,
-          children: [],
+          isOpen: oldEnvironmentState.isOpen || false,
+          children: oldEnvironmentState.children || [],
         });
       }
 
@@ -98,7 +102,7 @@ export default class EnvironmentsStore {
     * @return {Array}
     */
   toggleFolder(folder) {
-    return this.updateFolder(folder, 'isOpen', !folder.isOpen);
+    return this.updateEnvironmentProp(folder, 'isOpen', !folder.isOpen);
   }
 
   /**
@@ -125,23 +129,23 @@ export default class EnvironmentsStore {
       return updated;
     });
 
-    return this.updateFolder(folder, 'children', updatedEnvironments);
+    return this.updateEnvironmentProp(folder, 'children', updatedEnvironments);
   }
 
   /**
-   * Given a folder a prop and a new value updates the correct folder.
+   * Given a environment,  a prop and a new value updates the correct environment.
    *
-   * @param  {Object} folder
+   * @param  {Object} environment
    * @param  {String} prop
    * @param  {String|Boolean|Object|Array} newValue
    * @return {Array}
    */
-  updateFolder(folder, prop, newValue) {
+  updateEnvironmentProp(environment, prop, newValue) {
     const environments = this.state.environments;
 
     const updatedEnvironments = environments.map((env) => {
       const updateEnv = Object.assign({}, env);
-      if (env.isFolder && env.id === folder.id) {
+      if (env.id === environment.id) {
         updateEnv[prop] = newValue;
       }
 
@@ -149,8 +153,6 @@ export default class EnvironmentsStore {
     });
 
     this.state.environments = updatedEnvironments;
-
-    return updatedEnvironments;
   }
 
   getOpenFolders() {
diff --git a/app/assets/javascripts/experimental_flags.js b/app/assets/javascripts/experimental_flags.js
new file mode 100644
index 0000000000000000000000000000000000000000..dbd3843cef7db90d2d02a55f8295721d4260a1dc
--- /dev/null
+++ b/app/assets/javascripts/experimental_flags.js
@@ -0,0 +1,11 @@
+import Cookies from 'js-cookie';
+
+export default () => {
+  $('.js-experiment-feature-toggle').on('change', (e) => {
+    const el = e.target;
+
+    Cookies.set(el.name, el.value, {
+      expires: 365 * 10,
+    });
+  });
+};
diff --git a/app/assets/javascripts/files_comment_button.js b/app/assets/javascripts/files_comment_button.js
index 534e651b030b303056cdfa5449f957bbf922a891..d02e4cd5876a1a2778b01b459d064a66ef670283 100644
--- a/app/assets/javascripts/files_comment_button.js
+++ b/app/assets/javascripts/files_comment_button.js
@@ -1,150 +1,73 @@
 /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, max-len, one-var, one-var-declaration-per-line, quotes, prefer-template, newline-per-chained-call, comma-dangle, new-cap, no-else-return, consistent-return */
-/* global FilesCommentButton */
 /* global notes */
 
-let $commentButtonTemplate;
-
-window.FilesCommentButton = (function() {
-  var COMMENT_BUTTON_CLASS, EMPTY_CELL_CLASS, LINE_COLUMN_CLASSES, LINE_CONTENT_CLASS, LINE_HOLDER_CLASS, LINE_NUMBER_CLASS, OLD_LINE_CLASS, TEXT_FILE_SELECTOR, UNFOLDABLE_LINE_CLASS;
-
-  COMMENT_BUTTON_CLASS = '.add-diff-note';
-
-  LINE_HOLDER_CLASS = '.line_holder';
-
-  LINE_NUMBER_CLASS = 'diff-line-num';
-
-  LINE_CONTENT_CLASS = 'line_content';
-
-  UNFOLDABLE_LINE_CLASS = 'js-unfold';
-
-  EMPTY_CELL_CLASS = 'empty-cell';
-
-  OLD_LINE_CLASS = 'old_line';
-
-  LINE_COLUMN_CLASSES = "." + LINE_NUMBER_CLASS + ", .line_content";
-
-  TEXT_FILE_SELECTOR = '.text-file';
-
-  function FilesCommentButton(filesContainerElement) {
-    this.render = this.render.bind(this);
-    this.hideButton = this.hideButton.bind(this);
-    this.isParallelView = notes.isParallelView();
-    filesContainerElement.on('mouseover', LINE_COLUMN_CLASSES, this.render)
-      .on('mouseleave', LINE_COLUMN_CLASSES, this.hideButton);
-  }
-
-  FilesCommentButton.prototype.render = function(e) {
-    var $currentTarget, buttonParentElement, lineContentElement, textFileElement, $button;
-    $currentTarget = $(e.currentTarget);
-
-    if ($currentTarget.hasClass('js-no-comment-btn')) return;
-
-    lineContentElement = this.getLineContent($currentTarget);
-    buttonParentElement = this.getButtonParent($currentTarget);
-
-    if (!this.validateButtonParent(buttonParentElement) || !this.validateLineContent(lineContentElement)) return;
-
-    $button = $(COMMENT_BUTTON_CLASS, buttonParentElement);
-    buttonParentElement.addClass('is-over')
-      .nextUntil(`.${LINE_CONTENT_CLASS}`).addClass('is-over');
-
-    if ($button.length) {
-      return;
+/* Developer beware! Do not add logic to showButton or hideButton
+ * that will force a reflow. Doing so will create a signficant performance
+ * bottleneck for pages with large diffs. For a comprehensive list of what
+ * causes reflows, visit https://gist.github.com/paulirish/5d52fb081b3570c81e3a
+ */
+
+const LINE_NUMBER_CLASS = 'diff-line-num';
+const UNFOLDABLE_LINE_CLASS = 'js-unfold';
+const NO_COMMENT_CLASS = 'no-comment-btn';
+const EMPTY_CELL_CLASS = 'empty-cell';
+const OLD_LINE_CLASS = 'old_line';
+const LINE_COLUMN_CLASSES = `.${LINE_NUMBER_CLASS}, .line_content`;
+const DIFF_CONTAINER_SELECTOR = '.files';
+const DIFF_EXPANDED_CLASS = 'diff-expanded';
+
+export default {
+  init($diffFile) {
+    /* Caching is used only when the following members are *true*. This is because there are likely to be
+     * differently configured versions of diffs in the same session. However if these values are true, they
+     * will be true in all cases */
+
+    if (!this.userCanCreateNote) {
+      // data-can-create-note is an empty string when true, otherwise undefined
+      this.userCanCreateNote = $diffFile.closest(DIFF_CONTAINER_SELECTOR).data('can-create-note') === '';
     }
 
-    textFileElement = this.getTextFileElement($currentTarget);
-    buttonParentElement.append(this.buildButton({
-      discussionID: lineContentElement.attr('data-discussion-id'),
-      lineType: lineContentElement.attr('data-line-type'),
-
-      noteableType: textFileElement.attr('data-noteable-type'),
-      noteableID: textFileElement.attr('data-noteable-id'),
-      commitID: textFileElement.attr('data-commit-id'),
-      noteType: lineContentElement.attr('data-note-type'),
-
-      // LegacyDiffNote
-      lineCode: lineContentElement.attr('data-line-code'),
-
-      // DiffNote
-      position: lineContentElement.attr('data-position')
-    }));
-  };
-
-  FilesCommentButton.prototype.hideButton = function(e) {
-    var $currentTarget = $(e.currentTarget);
-    var buttonParentElement = this.getButtonParent($currentTarget);
-
-    buttonParentElement.removeClass('is-over')
-      .nextUntil(`.${LINE_CONTENT_CLASS}`).removeClass('is-over');
-  };
-
-  FilesCommentButton.prototype.buildButton = function(buttonAttributes) {
-    return $commentButtonTemplate.clone().attr({
-      'data-discussion-id': buttonAttributes.discussionID,
-      'data-line-type': buttonAttributes.lineType,
-
-      'data-noteable-type': buttonAttributes.noteableType,
-      'data-noteable-id': buttonAttributes.noteableID,
-      'data-commit-id': buttonAttributes.commitID,
-      'data-note-type': buttonAttributes.noteType,
-
-      // LegacyDiffNote
-      'data-line-code': buttonAttributes.lineCode,
-
-      // DiffNote
-      'data-position': buttonAttributes.position
-    });
-  };
-
-  FilesCommentButton.prototype.getTextFileElement = function(hoveredElement) {
-    return hoveredElement.closest(TEXT_FILE_SELECTOR);
-  };
-
-  FilesCommentButton.prototype.getLineContent = function(hoveredElement) {
-    if (hoveredElement.hasClass(LINE_CONTENT_CLASS)) {
-      return hoveredElement;
-    }
-    if (!this.isParallelView) {
-      return $(hoveredElement).closest(LINE_HOLDER_CLASS).find("." + LINE_CONTENT_CLASS);
-    } else {
-      return $(hoveredElement).next("." + LINE_CONTENT_CLASS);
+    if (typeof notes !== 'undefined' && !this.isParallelView) {
+      this.isParallelView = notes.isParallelView && notes.isParallelView();
     }
-  };
 
-  FilesCommentButton.prototype.getButtonParent = function(hoveredElement) {
-    if (!this.isParallelView) {
-      if (hoveredElement.hasClass(OLD_LINE_CLASS)) {
-        return hoveredElement;
-      }
-      return hoveredElement.parent().find("." + OLD_LINE_CLASS);
-    } else {
-      if (hoveredElement.hasClass(LINE_NUMBER_CLASS)) {
-        return hoveredElement;
-      }
-      return $(hoveredElement).prev("." + LINE_NUMBER_CLASS);
+    if (this.userCanCreateNote) {
+      $diffFile.on('mouseover', LINE_COLUMN_CLASSES, e => this.showButton(this.isParallelView, e))
+        .on('mouseleave', LINE_COLUMN_CLASSES, e => this.hideButton(this.isParallelView, e));
     }
-  };
+  },
 
-  FilesCommentButton.prototype.validateButtonParent = function(buttonParentElement) {
-    return !buttonParentElement.hasClass(EMPTY_CELL_CLASS) && !buttonParentElement.hasClass(UNFOLDABLE_LINE_CLASS);
-  };
+  showButton(isParallelView, e) {
+    const buttonParentElement = this.getButtonParent(e.currentTarget, isParallelView);
 
-  FilesCommentButton.prototype.validateLineContent = function(lineContentElement) {
-    return lineContentElement.attr('data-note-type') && lineContentElement.attr('data-note-type') !== '';
-  };
+    if (!this.validateButtonParent(buttonParentElement)) return;
 
-  return FilesCommentButton;
-})();
+    buttonParentElement.classList.add('is-over');
+    buttonParentElement.nextElementSibling.classList.add('is-over');
+  },
 
-$.fn.filesCommentButton = function() {
-  $commentButtonTemplate = $('<button name="button" type="submit" class="add-diff-note js-add-diff-note-button" title="Add a comment to this line"><i class="fa fa-comment-o"></i></button>');
+  hideButton(isParallelView, e) {
+    const buttonParentElement = this.getButtonParent(e.currentTarget, isParallelView);
 
-  if (!(this && (this.parent().data('can-create-note') != null))) {
-    return;
-  }
-  return this.each(function() {
-    if (!$.data(this, 'filesCommentButton')) {
-      return $.data(this, 'filesCommentButton', new FilesCommentButton($(this)));
+    buttonParentElement.classList.remove('is-over');
+    buttonParentElement.nextElementSibling.classList.remove('is-over');
+  },
+
+  getButtonParent(hoveredElement, isParallelView) {
+    if (isParallelView) {
+      if (!hoveredElement.classList.contains(LINE_NUMBER_CLASS)) {
+        return hoveredElement.previousElementSibling;
+      }
+    } else if (!hoveredElement.classList.contains(OLD_LINE_CLASS)) {
+      return hoveredElement.parentNode.querySelector(`.${OLD_LINE_CLASS}`);
     }
-  });
+    return hoveredElement;
+  },
+
+  validateButtonParent(buttonParentElement) {
+    return !buttonParentElement.classList.contains(EMPTY_CELL_CLASS) &&
+      !buttonParentElement.classList.contains(UNFOLDABLE_LINE_CLASS) &&
+      !buttonParentElement.classList.contains(NO_COMMENT_CLASS) &&
+      !buttonParentElement.parentNode.classList.contains(DIFF_EXPANDED_CLASS);
+  },
 };
diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js b/app/assets/javascripts/filtered_search/dropdown_hint.js
index 2af242a69df703a0c99c2bf77801be66a75c9a32..5838b1bdbb7bcdc428c0f727a835dd1b6f07a834 100644
--- a/app/assets/javascripts/filtered_search/dropdown_hint.js
+++ b/app/assets/javascripts/filtered_search/dropdown_hint.js
@@ -56,7 +56,7 @@ class DropdownHint extends gl.FilteredSearchDropdown {
   }
 
   renderContent() {
-    const dropdownData = gl.FilteredSearchTokenKeys.get()
+    const dropdownData = this.tokenKeys.get()
       .map(tokenKey => ({
         icon: `fa-${tokenKey.icon}`,
         hint: tokenKey.key,
diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js b/app/assets/javascripts/filtered_search/dropdown_user.js
index 65c1b2050acbfe990995a399e527b4da37b282af..19fed7711978f13ac5042a45ab635b9f8a801332 100644
--- a/app/assets/javascripts/filtered_search/dropdown_user.js
+++ b/app/assets/javascripts/filtered_search/dropdown_user.js
@@ -2,6 +2,7 @@
 
 import AjaxFilter from '~/droplab/plugins/ajax_filter';
 import './filtered_search_dropdown';
+import { addClassIfElementExists } from '../lib/utils/dom_utils';
 
 class DropdownUser extends gl.FilteredSearchDropdown {
   constructor(droplab, dropdown, input, tokenKeys, filter) {
@@ -32,8 +33,7 @@ class DropdownUser extends gl.FilteredSearchDropdown {
   }
 
   hideCurrentUser() {
-    const currenUserItem = this.dropdown.querySelector('.js-current-user');
-    currenUserItem.classList.add('hidden');
+    addClassIfElementExists(this.dropdown.querySelector('.js-current-user'), 'hidden');
   }
 
   itemClicked(e) {
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index 8f547bd8f1f894c69faa9bb4aa5ade391e1c4d68..7872e9e68add3c7860383234360e44b5655de226 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -3,6 +3,7 @@ import RecentSearchesRoot from './recent_searches_root';
 import RecentSearchesStore from './stores/recent_searches_store';
 import RecentSearchesService from './services/recent_searches_service';
 import eventHub from './event_hub';
+import { addClassIfElementExists } from '../lib/utils/dom_utils';
 
 class FilteredSearchManager {
   constructor(page) {
@@ -40,6 +41,10 @@ class FilteredSearchManager {
         return [];
       })
       .then((searches) => {
+        if (!searches) {
+          return;
+        }
+
         // Put any searches that may have come in before
         // we fetched the saved searches ahead of the already saved ones
         const resultantSearches = this.recentSearchesStore.setRecentSearches(
@@ -223,11 +228,7 @@ class FilteredSearchManager {
   }
 
   addInputContainerFocus() {
-    const inputContainer = this.filteredSearchInput.closest('.filtered-search-box');
-
-    if (inputContainer) {
-      inputContainer.classList.add('focus');
-    }
+    addClassIfElementExists(this.filteredSearchInput.closest('.filtered-search-box'), 'focus');
   }
 
   removeInputContainerFocus(e) {
@@ -487,6 +488,7 @@ class FilteredSearchManager {
   }
 
   searchState(e) {
+    e.preventDefault();
     const target = e.currentTarget;
     // remove focus outline after click
     target.blur();
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index 401dec1a37065eb5c3d56501f4793bcd7a3dc4ca..2c56b718212826bc6f9e6c4b358b5b6b4bc97e09 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -1,8 +1,5 @@
-import emojiMap from 'emojis/digests.json';
-import emojiAliases from 'emojis/aliases.json';
-import { glEmojiTag } from '~/behaviors/gl_emoji';
-import glRegexp from '~/lib/utils/regexp';
-import AjaxCache from '~/lib/utils/ajax_cache';
+import glRegexp from './lib/utils/regexp';
+import AjaxCache from './lib/utils/ajax_cache';
 
 function sanitize(str) {
   return str.replace(/<(?:.|\n)*?>/gm, '');
@@ -34,7 +31,7 @@ class GfmAutoComplete {
       const $input = $(input);
       $input.off('focus.setupAtWho').on('focus.setupAtWho', this.setupAtWho.bind(this, $input));
       // This triggers at.js again
-      // Needed for slash commands with suffixes (ex: /label ~)
+      // Needed for quick actions with suffixes (ex: /label ~)
       $input.on('inserted-commands.atwho', $input.trigger.bind($input, 'keyup'));
       $input.on('clear-commands-cache.atwho', () => this.clearCache());
     });
@@ -48,8 +45,8 @@ class GfmAutoComplete {
     if (this.enableMap.mergeRequests) this.setupMergeRequests($input);
     if (this.enableMap.labels) this.setupLabels($input);
 
-    // We don't instantiate the slash commands autocomplete for note and issue/MR edit forms
-    $input.filter('[data-supports-slash-commands="true"]').atwho({
+    // We don't instantiate the quick actions autocomplete for note and issue/MR edit forms
+    $input.filter('[data-supports-quick-actions="true"]').atwho({
       at: '/',
       alias: 'commands',
       searchKey: 'search',
@@ -375,7 +372,12 @@ class GfmAutoComplete {
     if (this.cachedData[at]) {
       this.loadData($input, at, this.cachedData[at]);
     } else if (GfmAutoComplete.atTypeMap[at] === 'emojis') {
-      this.loadData($input, at, Object.keys(emojiMap).concat(Object.keys(emojiAliases)));
+      import(/* webpackChunkName: 'emoji' */ './emoji')
+        .then(({ validEmojiNames, glEmojiTag }) => {
+          this.loadData($input, at, validEmojiNames);
+          GfmAutoComplete.glEmojiTag = glEmojiTag;
+        })
+        .catch(() => { this.isLoadingData[at] = false; });
     } else {
       AjaxCache.retrieve(this.dataSources[GfmAutoComplete.atTypeMap[at]], true)
         .then((data) => {
@@ -398,6 +400,13 @@ class GfmAutoComplete {
     this.cachedData = {};
   }
 
+  destroy() {
+    this.input.each((i, input) => {
+      const $input = $(input);
+      $input.atwho('destroy');
+    });
+  }
+
   static isLoading(data) {
     let dataToInspect = data;
     if (data && data.length > 0) {
@@ -423,12 +432,14 @@ GfmAutoComplete.atTypeMap = {
 };
 
 // Emoji
+GfmAutoComplete.glEmojiTag = null;
 GfmAutoComplete.Emoji = {
   templateFunction(name) {
-    return `<li>
-      ${name} ${glEmojiTag(name)}
-    </li>
-    `;
+    // glEmojiTag helper is loaded on-demand in fetchData()
+    if (GfmAutoComplete.glEmojiTag) {
+      return `<li>${name} ${GfmAutoComplete.glEmojiTag(name)}</li>`;
+    }
+    return `<li>${name}</li>`;
   },
 };
 // Team Members
diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js
index dc9f114af9940b728bac50d65747cc870905a56d..4e8141b2956e023ab323a3a5a937780fba8ab7c8 100644
--- a/app/assets/javascripts/gl_form.js
+++ b/app/assets/javascripts/gl_form.js
@@ -21,6 +21,9 @@ function GLForm(form, enableGFM = false) {
 GLForm.prototype.destroy = function() {
   // Clean form listeners
   this.clearEventListeners();
+  if (this.autoComplete) {
+    this.autoComplete.destroy();
+  }
   return this.form.data('gl-form', null);
 };
 
@@ -33,7 +36,8 @@ GLForm.prototype.setupForm = function() {
     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, .js-note-new-discussion'));
-    new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources).setup(this.form.find('.js-gfm-input'), {
+    this.autoComplete = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources);
+    this.autoComplete.setup(this.form.find('.js-gfm-input'), {
       emojis: true,
       members: this.enableGFM,
       issues: this.enableGFM,
diff --git a/app/assets/javascripts/group_name.js b/app/assets/javascripts/group_name.js
index 462d792b8d5348229b7346edef1b518f31502dd0..37c6765d94241725a765e7b61131106bfa7bdc5a 100644
--- a/app/assets/javascripts/group_name.js
+++ b/app/assets/javascripts/group_name.js
@@ -1,13 +1,13 @@
-
+import Cookies from 'js-cookie';
 import _ from 'underscore';
 
 export default class GroupName {
   constructor() {
-    this.titleContainer = document.querySelector('.title-container');
-    this.title = document.querySelector('.title');
+    this.titleContainer = document.querySelector('.js-title-container');
+    this.title = this.titleContainer.querySelector('.title');
     this.titleWidth = this.title.offsetWidth;
-    this.groupTitle = document.querySelector('.group-title');
-    this.groups = document.querySelectorAll('.group-path');
+    this.groupTitle = this.titleContainer.querySelector('.group-title');
+    this.groups = this.titleContainer.querySelectorAll('.group-path');
     this.toggle = null;
     this.isHidden = false;
     this.init();
@@ -33,11 +33,20 @@ export default class GroupName {
 
   createToggle() {
     this.toggle = document.createElement('button');
+    this.toggle.setAttribute('type', 'button');
     this.toggle.className = 'text-expander group-name-toggle';
     this.toggle.setAttribute('aria-label', 'Toggle full path');
-    this.toggle.innerHTML = '...';
+    if (Cookies.get('new_nav') === 'true') {
+      this.toggle.innerHTML = '<i class="fa fa-ellipsis-h" aria-hidden="true"></i>';
+    } else {
+      this.toggle.innerHTML = '...';
+    }
     this.toggle.addEventListener('click', this.toggleGroups.bind(this));
-    this.titleContainer.insertBefore(this.toggle, this.title);
+    if (Cookies.get('new_nav') === 'true') {
+      this.title.insertBefore(this.toggle, this.groupTitle);
+    } else {
+      this.titleContainer.insertBefore(this.toggle, this.title);
+    }
     this.toggleGroups();
   }
 
diff --git a/app/assets/javascripts/groups/stores/groups_store.js b/app/assets/javascripts/groups/stores/groups_store.js
index f6dc4290fd5e5517fd83cf8f4c4ee8ffbcf0179d..6eab6083e8fc9310782a85b62610bff370aaf001 100644
--- a/app/assets/javascripts/groups/stores/groups_store.js
+++ b/app/assets/javascripts/groups/stores/groups_store.js
@@ -47,8 +47,8 @@ export default class GroupsStore {
 
     // Map groups to an object
     groups.map((group) => {
-      mappedGroups[group.id] = group;
-      mappedGroups[group.id].subGroups = {};
+      mappedGroups[`id${group.id}`] = group;
+      mappedGroups[`id${group.id}`].subGroups = {};
       return group;
     });
 
@@ -56,26 +56,27 @@ export default class GroupsStore {
       const currentGroup = mappedGroups[key];
       if (currentGroup.parentId) {
         // If the group is not at the root level, add it to its parent array of subGroups.
-        const findParentGroup = mappedGroups[currentGroup.parentId];
+        const findParentGroup = mappedGroups[`id${currentGroup.parentId}`];
         if (findParentGroup) {
-          mappedGroups[currentGroup.parentId].subGroups[currentGroup.id] = currentGroup;
-          mappedGroups[currentGroup.parentId].isOpen = true; // Expand group if it has subgroups
+          mappedGroups[`id${currentGroup.parentId}`].subGroups[`id${currentGroup.id}`] = currentGroup;
+          mappedGroups[`id${currentGroup.parentId}`].isOpen = true; // Expand group if it has subgroups
         } else if (parentGroup && parentGroup.id === currentGroup.parentId) {
-          tree[currentGroup.id] = currentGroup;
+          tree[`id${currentGroup.id}`] = currentGroup;
         } else {
-          // Means the groups hast no direct parent.
-          // Save for later processing, we will add them to its corresponding base group
+          // No parent found. We save it for later processing
           orphans.push(currentGroup);
+
+          // Add to tree to preserve original order
+          tree[`id${currentGroup.id}`] = currentGroup;
         }
       } else {
-        // If the group is at the root level, add it to first level elements array.
-        tree[currentGroup.id] = currentGroup;
+        // If the group is at the top level, add it to first level elements array.
+        tree[`id${currentGroup.id}`] = currentGroup;
       }
 
       return key;
     });
 
-    // Hopefully this array will be empty for most cases
     if (orphans.length) {
       orphans.map((orphan) => {
         let found = false;
@@ -83,11 +84,23 @@ export default class GroupsStore {
 
         Object.keys(tree).map((key) => {
           const group = tree[key];
-          if (currentOrphan.fullPath.lastIndexOf(group.fullPath) === 0) {
+
+          if (
+           group &&
+           currentOrphan.fullPath.lastIndexOf(group.fullPath) === 0 &&
+           // Make sure the currently selected orphan is not the same as the group
+           // we are checking here otherwise it will end up in an infinite loop
+           currentOrphan.id !== group.id
+           ) {
             group.subGroups[currentOrphan.id] = currentOrphan;
             group.isOpen = true;
             currentOrphan.isOrphan = true;
             found = true;
+
+            // Delete if group was put at the top level. If not the group will be displayed twice.
+            if (tree[`id${currentOrphan.id}`]) {
+              delete tree[`id${currentOrphan.id}`];
+            }
           }
 
           return key;
@@ -95,7 +108,8 @@ export default class GroupsStore {
 
         if (!found) {
           currentOrphan.isOrphan = true;
-          tree[currentOrphan.id] = currentOrphan;
+
+          tree[`id${currentOrphan.id}`] = currentOrphan;
         }
 
         return orphan;
@@ -140,7 +154,7 @@ export default class GroupsStore {
 
   // eslint-disable-next-line class-methods-use-this
   removeGroup(group, collection) {
-    Vue.delete(collection, group.id);
+    Vue.delete(collection, `id${group.id}`);
   }
 
   // eslint-disable-next-line class-methods-use-this
diff --git a/app/assets/javascripts/issuable_bulk_update_sidebar.js b/app/assets/javascripts/issuable_bulk_update_sidebar.js
index 84bd2e092e6d4177ad0190da680c28bbf9391550..4f376599ba98a6ed4365e6140ee45ac316e4a306 100644
--- a/app/assets/javascripts/issuable_bulk_update_sidebar.js
+++ b/app/assets/javascripts/issuable_bulk_update_sidebar.js
@@ -5,6 +5,7 @@
 /* global SubscriptionSelect */
 
 import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
+import SidebarHeightManager from './sidebar_height_manager';
 
 const HIDDEN_CLASS = 'hidden';
 const DISABLED_CONTENT_CLASS = 'disabled-content';
@@ -22,6 +23,7 @@ export default class IssuableBulkUpdateSidebar {
   initDomElements() {
     this.$page = $('.page-with-sidebar');
     this.$sidebar = $('.right-sidebar');
+    this.$sidebarInnerContainer = this.$sidebar.find('.issuable-sidebar');
     this.$bulkEditCancelBtn = $('.js-bulk-update-menu-hide');
     this.$bulkEditSubmitBtn = $('.update-selected-issues');
     this.$bulkUpdateEnableBtn = $('.js-bulk-update-toggle');
@@ -55,18 +57,6 @@ export default class IssuableBulkUpdateSidebar {
     return navbarHeight + layoutNavHeight + subNavScroll;
   }
 
-  initSidebar() {
-    if (!this.navHeight) {
-      this.navHeight = this.getNavHeight();
-    }
-
-    if (!this.sidebarInitialized) {
-      $(document).off('scroll').on('scroll', _.throttle(this.setSidebarHeight, 10).bind(this));
-      $(window).off('resize').on('resize', _.throttle(this.setSidebarHeight, 10).bind(this));
-      this.sidebarInitialized = true;
-    }
-  }
-
   setupBulkUpdateActions() {
     IssuableBulkUpdateActions.setOriginalDropdownData();
   }
@@ -96,7 +86,7 @@ export default class IssuableBulkUpdateSidebar {
     this.toggleCheckboxDisplay(enable);
 
     if (enable) {
-      this.initSidebar();
+      SidebarHeightManager.init();
     }
   }
 
@@ -113,6 +103,7 @@ export default class IssuableBulkUpdateSidebar {
   toggleSidebarDisplay(show) {
     this.$page.toggleClass(SIDEBAR_EXPANDED_CLASS, show);
     this.$page.toggleClass(SIDEBAR_COLLAPSED_CLASS, !show);
+    this.$sidebarInnerContainer.toggleClass(HIDDEN_CLASS, !show);
     this.$sidebar.toggleClass(SIDEBAR_EXPANDED_CLASS, show);
     this.$sidebar.toggleClass(SIDEBAR_COLLAPSED_CLASS, !show);
   }
@@ -141,17 +132,6 @@ export default class IssuableBulkUpdateSidebar {
       this.$bulkEditSubmitBtn.enable();
     }
   }
-  // loosely based on method of the same name in right_sidebar.js
-  setSidebarHeight() {
-    const currentScrollDepth = window.pageYOffset || 0;
-    const diff = this.navHeight - currentScrollDepth;
-
-    if (diff > 0) {
-      this.$sidebar.outerHeight(window.innerHeight - diff);
-    } else {
-      this.$sidebar.outerHeight('100%');
-    }
-  }
 
   static getCheckedIssueIds() {
     const $checkedIssues = $('.selected_issue:checked');
diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue
index e14414d3f68c7e1e46911ea5f8a80cdefab203fe..3d5fb7f441ca37dadfa4b0e559d2b295d778f613 100644
--- a/app/assets/javascripts/issue_show/components/app.vue
+++ b/app/assets/javascripts/issue_show/components/app.vue
@@ -51,6 +51,11 @@ export default {
       required: false,
       default: '',
     },
+    initialTaskStatus: {
+      type: String,
+      required: false,
+      default: '',
+    },
     updatedAt: {
       type: String,
       required: false,
@@ -105,6 +110,7 @@ export default {
       updatedAt: this.updatedAt,
       updatedByName: this.updatedByName,
       updatedByPath: this.updatedByPath,
+      taskStatus: this.initialTaskStatus,
     });
 
     return {
@@ -198,13 +204,7 @@ export default {
       method: 'getData',
       successCallback: (res) => {
         const data = res.json();
-        const shouldUpdate = this.store.stateShouldUpdate(data);
-
         this.store.updateState(data);
-
-        if (this.showForm && (shouldUpdate.title || shouldUpdate.description)) {
-          this.store.formState.lockedWarningVisible = true;
-        }
       },
       errorCallback(err) {
         throw new Error(err);
diff --git a/app/assets/javascripts/issue_show/components/description.vue b/app/assets/javascripts/issue_show/components/description.vue
index 5ae617356e020a07c3dfaa1f7a1c826928782aa5..43db66c8e08bb3ea3618c4731124c286ced100d5 100644
--- a/app/assets/javascripts/issue_show/components/description.vue
+++ b/app/assets/javascripts/issue_show/components/description.vue
@@ -37,23 +37,12 @@
         });
       },
       taskStatus() {
-        const taskRegexMatches = this.taskStatus.match(/(\d+) of (\d+)/);
-        const $issuableHeader = $('.issuable-meta');
-        const $tasks = $('#task_status', $issuableHeader);
-        const $tasksShort = $('#task_status_short', $issuableHeader);
-
-        if (taskRegexMatches) {
-          $tasks.text(this.taskStatus);
-          $tasksShort.text(`${taskRegexMatches[1]}/${taskRegexMatches[2]} task${taskRegexMatches[2] > 1 ? 's' : ''}`);
-        } else {
-          $tasks.text('');
-          $tasksShort.text('');
-        }
+        this.updateTaskStatusText();
       },
     },
     methods: {
       renderGFM() {
-        $(this.$refs['gfm-entry-content']).renderGFM();
+        $(this.$refs['gfm-content']).renderGFM();
 
         if (this.canUpdate) {
           // eslint-disable-next-line no-new
@@ -64,9 +53,24 @@
           });
         }
       },
+      updateTaskStatusText() {
+        const taskRegexMatches = this.taskStatus.match(/(\d+) of ((?!0)\d+)/);
+        const $issuableHeader = $('.issuable-meta');
+        const $tasks = $('#task_status', $issuableHeader);
+        const $tasksShort = $('#task_status_short', $issuableHeader);
+
+        if (taskRegexMatches) {
+          $tasks.text(this.taskStatus);
+          $tasksShort.text(`${taskRegexMatches[1]}/${taskRegexMatches[2]} task${taskRegexMatches[2] > 1 ? 's' : ''}`);
+        } else {
+          $tasks.text('');
+          $tasksShort.text('');
+        }
+      },
     },
     mounted() {
       this.renderGFM();
+      this.updateTaskStatusText();
     },
   };
 </script>
diff --git a/app/assets/javascripts/issue_show/components/fields/description.vue b/app/assets/javascripts/issue_show/components/fields/description.vue
index 30a1be5cb50ef16ea43b8d7cd47253192e65a2cf..27b1b814f9a5baf98b6cec2e73facf95a17f68da 100644
--- a/app/assets/javascripts/issue_show/components/fields/description.vue
+++ b/app/assets/javascripts/issue_show/components/fields/description.vue
@@ -41,13 +41,14 @@
       <textarea
         id="issue-description"
         class="note-textarea js-gfm-input js-autosize markdown-area"
-        data-supports-slash-commands="false"
+        data-supports-quick-actionss="false"
         aria-label="Description"
         v-model="formState.description"
         ref="textarea"
         slot="textarea"
         placeholder="Write a comment or drag your files here..."
-        @keydown.meta.enter="updateIssuable">
+        @keydown.meta.enter="updateIssuable"
+        @keydown.ctrl.enter="updateIssuable">
       </textarea>
     </markdown-field>
   </div>
diff --git a/app/assets/javascripts/issue_show/components/fields/project_move.vue b/app/assets/javascripts/issue_show/components/fields/project_move.vue
index f811fb0de24e2d9e0ace87463c4f01b63ca20e17..7bf2be8b28a64a368adc30a2e179ce4799930a5b 100644
--- a/app/assets/javascripts/issue_show/components/fields/project_move.vue
+++ b/app/assets/javascripts/issue_show/components/fields/project_move.vue
@@ -1,10 +1,10 @@
 <script>
-  import tooltipMixin from '../../../vue_shared/mixins/tooltip';
+  import tooltip from '../../../vue_shared/directives/tooltip';
 
   export default {
-    mixins: [
-      tooltipMixin,
-    ],
+    directives: {
+      tooltip,
+    },
     props: {
       formState: {
         type: Object,
@@ -71,9 +71,9 @@
         data-placeholder="Move to a different project" />
     </div>
     <span
+      v-tooltip
       data-placement="auto top"
-      title="Moving an issue will copy the discussion to a different project and close it here. All participants will be notified of the new location."
-      ref="tooltip">
+      title="Moving an issue will copy the discussion to a different project and close it here. All participants will be notified of the new location.">
       <i
         class="fa fa-question-circle"
         aria-hidden="true">
diff --git a/app/assets/javascripts/issue_show/components/fields/title.vue b/app/assets/javascripts/issue_show/components/fields/title.vue
index 6556bf117e2d7b3c129f703c28aec78c4db49334..83af8e1e2451b742879c89325b4f49d9d4f40122 100644
--- a/app/assets/javascripts/issue_show/components/fields/title.vue
+++ b/app/assets/javascripts/issue_show/components/fields/title.vue
@@ -26,6 +26,7 @@
       placeholder="Issue title"
       aria-label="Issue title"
       v-model="formState.title"
-      @keydown.meta.enter="updateIssuable" />
+      @keydown.meta.enter="updateIssuable"
+      @keydown.ctrl.enter="updateIssuable" />
   </fieldset>
 </template>
diff --git a/app/assets/javascripts/issue_show/index.js b/app/assets/javascripts/issue_show/index.js
index 14b2a1e18e910179b1beb5250a34ea40d4d70c07..ad8cb6465e281d8306385fec909a9b70dbe9eade 100644
--- a/app/assets/javascripts/issue_show/index.js
+++ b/app/assets/javascripts/issue_show/index.js
@@ -45,6 +45,7 @@ document.addEventListener('DOMContentLoaded', () => {
           updatedAt: this.updatedAt,
           updatedByName: this.updatedByName,
           updatedByPath: this.updatedByPath,
+          initialTaskStatus: this.initialTaskStatus,
         },
       });
     },
diff --git a/app/assets/javascripts/issue_show/stores/index.js b/app/assets/javascripts/issue_show/stores/index.js
index 27c2d349f52b2c69dbb99e2a8652c3e889ae0dc1..0c8bd6f1cc34604540354c223727c205000b2e9f 100644
--- a/app/assets/javascripts/issue_show/stores/index.js
+++ b/app/assets/javascripts/issue_show/stores/index.js
@@ -1,23 +1,6 @@
 export default class Store {
-  constructor({
-    titleHtml,
-    titleText,
-    descriptionHtml,
-    descriptionText,
-    updatedAt,
-    updatedByName,
-    updatedByPath,
-  }) {
-    this.state = {
-      titleHtml,
-      titleText,
-      descriptionHtml,
-      descriptionText,
-      taskStatus: '',
-      updatedAt,
-      updatedByName,
-      updatedByPath,
-    };
+  constructor(initialState) {
+    this.state = initialState;
     this.formState = {
       title: '',
       confidential: false,
@@ -29,6 +12,10 @@ export default class Store {
   }
 
   updateState(data) {
+    if (this.stateShouldUpdate(data)) {
+      this.formState.lockedWarningVisible = true;
+    }
+
     this.state.titleHtml = data.title;
     this.state.titleText = data.title_text;
     this.state.descriptionHtml = data.description;
@@ -40,10 +27,8 @@ export default class Store {
   }
 
   stateShouldUpdate(data) {
-    return {
-      title: this.state.titleText !== data.title_text,
-      description: this.state.descriptionText !== data.description_text,
-    };
+    return this.state.titleText !== data.title_text ||
+      this.state.descriptionText !== data.description_text;
   }
 
   setFormState(state) {
diff --git a/app/assets/javascripts/jobs/components/sidebar_details_block.vue b/app/assets/javascripts/jobs/components/sidebar_details_block.vue
index 4223a8fea497b58666c412ce8b6b87c8052c7a40..d0145fed3964908dfb84c697489e8fb50383214b 100644
--- a/app/assets/javascripts/jobs/components/sidebar_details_block.vue
+++ b/app/assets/javascripts/jobs/components/sidebar_details_block.vue
@@ -39,6 +39,17 @@
       runnerId() {
         return `#${this.job.runner.id}`;
       },
+      renderBlock() {
+        return this.job.merge_request ||
+          this.job.duration ||
+          this.job.finished_data ||
+          this.job.erased_at ||
+          this.job.queued ||
+          this.job.runner ||
+          this.job.coverage ||
+          this.job.tags.length ||
+          this.job.cancel_path;
+      },
     },
   };
 </script>
@@ -63,7 +74,7 @@
           Retry
         </a>
       </div>
-      <div class="block">
+      <div :class="{block : renderBlock }">
         <p
           class="build-detail-row js-job-mr"
           v-if="job.merge_request">
diff --git a/app/assets/javascripts/jobs/job_details_bundle.js b/app/assets/javascripts/jobs/job_details_bundle.js
index 939d17129de9c26a2052be70e898d56a79e7a973..f92e669414a9d2bb4a864d47ee4602805e45ff51 100644
--- a/app/assets/javascripts/jobs/job_details_bundle.js
+++ b/app/assets/javascripts/jobs/job_details_bundle.js
@@ -26,14 +26,6 @@ document.addEventListener('DOMContentLoaded', () => {
     mounted() {
       this.mediator.initBuildClass();
     },
-    updated() {
-      // Wait for flash message to be appended
-      Vue.nextTick(() => {
-        if (this.mediator.build) {
-          this.mediator.build.verifyTopPosition();
-        }
-      });
-    },
     render(createElement) {
       return createElement('job-header', {
         props: {
diff --git a/app/assets/javascripts/label_manager.js b/app/assets/javascripts/label_manager.js
index 38b2eb9ff14f52a86ad46e5a5d42de8b2c56db66..d8814802d9ef4324f2ba97410e01abd981691378 100644
--- a/app/assets/javascripts/label_manager.js
+++ b/app/assets/javascripts/label_manager.js
@@ -21,6 +21,7 @@
     }
 
     bindEvents() {
+      this.prioritizedLabels.find('.btn-action').on('mousedown', this, this.onButtonActionClick);
       return this.togglePriorityButton.on('click', this, this.onTogglePriorityClick);
     }
 
@@ -36,6 +37,11 @@
       _this.toggleEmptyState($label, $btn, action);
     }
 
+    onButtonActionClick(e) {
+      e.stopPropagation();
+      $(e.currentTarget).tooltip('hide');
+    }
+
     toggleEmptyState($label, $btn, action) {
       this.emptyState.classList.toggle('hidden', !!this.prioritizedLabels[0].querySelector(':scope > li'));
     }
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index 2aca86189fd21723eb0a2ceb9fdda0e1a3dc378a..122ec138c59cb70ceb653b93b3847ff5f8326c06 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -86,18 +86,25 @@
       // This is required to handle non-unicode characters in hash
       hash = decodeURIComponent(hash);
 
+      var fixedTabs = document.querySelector('.js-tabs-affix');
+      var fixedNav = document.querySelector('.navbar-gitlab');
+
+      var adjustment = 0;
+      if (fixedNav) adjustment -= fixedNav.offsetHeight;
+
       // scroll to user-generated markdown anchor if we cannot find a match
       if (document.getElementById(hash) === null) {
         var target = document.getElementById('user-content-' + hash);
         if (target && target.scrollIntoView) {
           target.scrollIntoView(true);
+          window.scrollBy(0, adjustment);
         }
       } else {
         // only adjust for fixedTabs when not targeting user-generated content
-        var fixedTabs = document.querySelector('.js-tabs-affix');
         if (fixedTabs) {
-          window.scrollBy(0, -fixedTabs.offsetHeight);
+          adjustment -= fixedTabs.offsetHeight;
         }
+        window.scrollBy(0, adjustment);
       }
     };
 
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index 54c0da3fc9c9415a6233517871900d5dc60566fe..1d1763c39631b9e6707641b3d8b96f4bbd0f10a2 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -34,7 +34,7 @@ window.dateFormat = dateFormat;
 
     w.gl.utils.localTimeAgo = function($timeagoEls, setTimeago = true) {
       $timeagoEls.each((i, el) => {
-        el.setAttribute('title', gl.utils.formatDate(el.getAttribute('datetime')));
+        el.setAttribute('title', el.getAttribute('title'));
 
         if (setTimeago) {
           // Recreate with custom template
@@ -112,29 +112,11 @@ window.dateFormat = dateFormat;
       return timefor;
     };
 
-    w.gl.utils.cachedTimeagoElements = [];
     w.gl.utils.renderTimeago = function($els) {
-      if (!$els && !w.gl.utils.cachedTimeagoElements.length) {
-        w.gl.utils.cachedTimeagoElements = [].slice.call(document.querySelectorAll('.js-timeago-render'));
-      } else if ($els) {
-        w.gl.utils.cachedTimeagoElements = w.gl.utils.cachedTimeagoElements.concat($els.toArray());
-      }
-
-      w.gl.utils.cachedTimeagoElements.forEach(gl.utils.updateTimeagoText);
-    };
-
-    w.gl.utils.updateTimeagoText = function(el) {
-      const formattedDate = gl.utils.getTimeago().format(el.getAttribute('datetime'), lang);
-
-      if (el.textContent !== formattedDate) {
-        el.textContent = formattedDate;
-      }
-    };
-
-    w.gl.utils.initTimeagoTimeout = function() {
-      gl.utils.renderTimeago();
+      const timeagoEls = $els || document.querySelectorAll('.js-timeago-render');
 
-      gl.utils.timeagoTimeout = setTimeout(gl.utils.initTimeagoTimeout, 1000);
+      // timeago.js sets timeouts internally for each timeago value to be updated in real time
+      gl.utils.getTimeago().render(timeagoEls, lang);
     };
 
     w.gl.utils.getDayDifference = function(a, b) {
diff --git a/app/assets/javascripts/lib/utils/dom_utils.js b/app/assets/javascripts/lib/utils/dom_utils.js
new file mode 100644
index 0000000000000000000000000000000000000000..de65ea15a6034185d5f7b1a9ccee1ea69b7c60d2
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/dom_utils.js
@@ -0,0 +1,7 @@
+/* eslint-disable import/prefer-default-export */
+
+export const addClassIfElementExists = (element, className) => {
+  if (element) {
+    element.classList.add(className);
+  }
+};
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
index 601d01e1be111b10708532b1d919950422e158ac..021f936a4fa7d3433703c612900d5963922c6c6f 100644
--- a/app/assets/javascripts/lib/utils/text_utility.js
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -94,8 +94,8 @@ gl.text.insertText = function(textArea, text, tag, blockTag, selected, wrap) {
 
   startChar = !wrap && !currentLineEmpty && textArea.selectionStart > 0 ? '\n' : '';
 
-  if (selectedSplit.length > 1 && (!wrap || (blockTag != null))) {
-    if (blockTag != null) {
+  if (selectedSplit.length > 1 && (!wrap || (blockTag != null && blockTag !== ''))) {
+    if (blockTag != null && blockTag !== '') {
       insertText = this.blockTagText(text, textArea, blockTag, selected);
     } else {
       insertText = selectedSplit.map(function(val) {
diff --git a/app/assets/javascripts/locale/bg/app.js b/app/assets/javascripts/locale/bg/app.js
deleted file mode 100644
index ba56c0bea25acdd7da2f68ae9350772120459571..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/locale/bg/app.js
+++ /dev/null
@@ -1 +0,0 @@
-var locales = locales || {}; locales['bg'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-05-04 19:24-0500","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-05 09:40-0400","Last-Translator":"Lyubomir Vasilev <lyubomirv@abv.bg>","Language-Team":"Bulgarian","Language":"bg","X-Generator":"Zanata 3.9.6","Plural-Forms":"nplurals=2; plural=(n != 1)","lang":"bg","domain":"app","plural_forms":"nplurals=2; plural=(n != 1)"},"ByAuthor|by":["от"],"Commit":["Подаване","Подавания"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["Анализът на циклите дава общ поглед върху това колко време е нужно на една идея да се превърне в завършена функционалност в проекта."],"CycleAnalyticsStage|Code":["Програмиране"],"CycleAnalyticsStage|Issue":["Проблем"],"CycleAnalyticsStage|Plan":["Планиране"],"CycleAnalyticsStage|Production":["Издаване"],"CycleAnalyticsStage|Review":["Преглед и одобрение"],"CycleAnalyticsStage|Staging":["Подготовка за издаване"],"CycleAnalyticsStage|Test":["Тестване"],"Deploy":["Внедряване","Внедрявания"],"FirstPushedBy|First":["Първо"],"FirstPushedBy|pushed by":["изпращане на промени от"],"From issue creation until deploy to production":["От създаването на проблема до внедряването в крайната версия"],"From merge request merge until deploy to production":["От прилагането на заявката за сливане до внедряването в крайната версия"],"Introducing Cycle Analytics":["Представяме Ви анализът на циклите"],"Last %d day":["Последния %d ден","Последните %d дни"],"Limited to showing %d event at most":["Ограничено до показване на последното %d събитие","Ограничено до показване на последните %d събития"],"Median":["Медиана"],"New Issue":["Нов проблем","Нови проблема"],"Not available":["Не е налично"],"Not enough data":["Няма достатъчно данни"],"OpenedNDaysAgo|Opened":["Отворен"],"Pipeline Health":["Състояние"],"ProjectLifecycle|Stage":["Етап"],"Read more":["Прочетете повече"],"Related Commits":["Свързани подавания"],"Related Deployed Jobs":["Свързани задачи за внедряване"],"Related Issues":["Свързани проблеми"],"Related Jobs":["Свързани задачи"],"Related Merge Requests":["Свързани заявки за сливане"],"Related Merged Requests":["Свързани приложени заявки за сливане"],"Showing %d event":["Показване на %d събитие","Показване на %d събития"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["Етапът на програмиране показва времето от първото подаване до създаването на заявката за сливане. Данните ще бъдат добавени тук автоматично след като бъде създадена първата заявка за сливане."],"The collection of events added to the data gathered for that stage.":["Съвкупността от събития добавени към данните събрани за този етап."],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["Етапът на проблемите показва колко е времето от създаването на проблем до определянето на целеви етап на проекта за него, или до добавянето му в списък на дъската за проблеми. Започнете да добавяте проблеми, за да видите данните за този етап."],"The phase of the development lifecycle.":["Етапът от цикъла на разработка"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["Етапът на планиране показва колко е времето от преходната стъпка до изпращането на първото подаване. Това време ще бъде добавено автоматично след като изпратите първото си подаване."],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["Етапът на издаване показва общото време, което е нужно от създаването на проблем до внедряването на кода в крайната версия."],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["Етапът на преглед и одобрение показва времето от създаването на заявката за сливане до прилагането ѝ. Данните ще бъдат добавени автоматично след като приложите първата си заявка за сливане."],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["Етапът на подготовка за издаване показва времето между прилагането на заявката за сливане и внедряването на кода в средата на работещата крайна версия. Данните ще бъдат добавени автоматично след като направите първото си внедряване в крайната версия."],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["Етапът на тестване показва времето, което е нужно на „Gitlab CI“ да изпълни всички задачи за свързаната заявка за сливане. Данните ще бъдат добавени автоматично след като приключи изпълнените на първата Ви такава задача."],"The time taken by each data entry gathered by that stage.":["Времето, което отнема всеки запис от данни за съответния етап."],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["Стойността, която се намира в средата на последователността от наблюдавани данни. Например: медианата на 3, 5 и 9 е 5, а медианата на 3, 5, 7 и 8 е (5+7)/2 = 6."],"Time before an issue gets scheduled":["Време преди един проблем да бъде планиран за работа"],"Time before an issue starts implementation":["Време преди работата по проблем да започне"],"Time between merge request creation and merge/close":["Време между създаване на заявка за сливане и прилагането/отхвърлянето ѝ"],"Time until first merge request":["Време преди първата заявка за сливане"],"Time|hr":["час","часа"],"Time|min":["мин","мин"],"Time|s":["сек"],"Total Time":["Общо време"],"Total test time for all commits/merges":["Общо време за тестване на всички подавания/сливания"],"Want to see the data? Please ask an administrator for access.":["Искате ли да видите данните? Помолете администратор за достъп."],"We don't have enough data to show this stage.":["Няма достатъчно данни за този етап."],"You need permission.":["Нуждаете се от разрешение."],"day":["ден","дни"]}}};
\ No newline at end of file
diff --git a/app/assets/javascripts/locale/de/app.js b/app/assets/javascripts/locale/de/app.js
deleted file mode 100644
index e7d2b174405f1410362bda7e5003d02c364cc5fc..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/locale/de/app.js
+++ /dev/null
@@ -1 +0,0 @@
-var locales = locales || {}; locales['de'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","PO-Revision-Date":"2017-05-09 13:44+0200","Language-Team":"German","Language":"de","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Plural-Forms":"nplurals=2; plural=n != 1;","Last-Translator":"","X-Generator":"Poedit 2.0.1","lang":"de","domain":"app","plural_forms":"nplurals=2; plural=n != 1;"},"Are you sure you want to delete this pipeline schedule?":[""],"ByAuthor|by":["Von"],"Cancel":[""],"Commit":["Commit","Commits"],"Cron Timezone":[""],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["Cycle Analytics liefern einen Überblick darüber, wie viel Zeit in Ihrem Projekt von einer Idee bis zum Produktivdeployment vergeht."],"CycleAnalyticsStage|Code":["Code"],"CycleAnalyticsStage|Issue":["Issue"],"CycleAnalyticsStage|Plan":["Planung"],"CycleAnalyticsStage|Production":["Produktiv"],"CycleAnalyticsStage|Review":["Review"],"CycleAnalyticsStage|Staging":["Staging"],"CycleAnalyticsStage|Test":["Test"],"Delete":[""],"Deploy":["Deployment","Deployments"],"Description":[""],"Edit":[""],"Edit Pipeline Schedule %{id}":[""],"Failed to change the owner":[""],"Failed to remove the pipeline schedule":[""],"Filter":[""],"FirstPushedBy|First":["Erster"],"FirstPushedBy|pushed by":["gepusht von"],"From issue creation until deploy to production":["Vom Anlegen des Issues bis zum Produktivdeployment"],"From merge request merge until deploy to production":["Vom Merge Request bis zum Produktivdeployment"],"Interval Pattern":[""],"Introducing Cycle Analytics":["Was sind Cycle Analytics?"],"Last %d day":["Letzter %d Tag","Letzten %d Tage"],"Last Pipeline":[""],"Limited to showing %d event at most":["Eingeschränkt auf maximal %d Ereignis","Eingeschränkt auf maximal %d Ereignisse"],"Median":["Median"],"New Issue":["Neues Issue","Neue Issues"],"New Pipeline Schedule":[""],"No schedules":[""],"Not available":["Nicht verfügbar"],"Not enough data":["Nicht genügend Daten"],"OpenedNDaysAgo|Opened":["Erstellt"],"Owner":[""],"Pipeline Health":["Pipeline Kennzahlen"],"Pipeline Schedule":[""],"Pipeline Schedules":[""],"PipelineSchedules|Activated":[""],"PipelineSchedules|Active":[""],"PipelineSchedules|All":[""],"PipelineSchedules|Inactive":[""],"PipelineSchedules|Next Run":[""],"PipelineSchedules|None":[""],"PipelineSchedules|Provide a short description for this pipeline":[""],"PipelineSchedules|Take ownership":[""],"PipelineSchedules|Target":[""],"ProjectLifecycle|Stage":["Phase"],"Read more":["Mehr"],"Related Commits":["Zugehörige Commits"],"Related Deployed Jobs":["Zugehörige Deploymentjobs"],"Related Issues":["Zugehörige Issues"],"Related Jobs":["Zugehörige Jobs"],"Related Merge Requests":["Zugehörige Merge Requests"],"Related Merged Requests":["Zugehörige abgeschlossene Merge Requests"],"Save pipeline schedule":[""],"Schedule a new pipeline":[""],"Select a timezone":[""],"Select target branch":[""],"Showing %d event":["Zeige %d Ereignis","Zeige %d Ereignisse"],"Target Branch":[""],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["Die Code-Phase stellt die Zeit vom ersten Commit bis zum Erstellen eines Merge Requests dar. Sobald Sie Ihren ersten Merge Request anlegen, werden dessen Daten automatisch ergänzt."],"The collection of events added to the data gathered for that stage.":["Ereignisse, die für diese Phase ausgewertet wurden."],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["Die Issue-Phase stellt die Zeit vom Anlegen eines Issues bis zum Zuweisen eines Meilensteins oder Hinzufügen zum Issue Board dar. Erstellen Sie einen Issue, damit dessen Daten hier erscheinen."],"The phase of the development lifecycle.":["Die Phase im Entwicklungsprozess."],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["Die Planungsphase stellt die Zeit von der vorherigen Phase bis zum Pushen des ersten Commits dar. Sobald Sie den ersten Commit pushen, werden dessen Daten hier erscheinen."],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["Die Produktiv-Phase stellt die Gesamtzeit vom Anlegen eines Issues bis zum Deployment auf dem Produktivsystem dar. Sobald Sie den vollständigen Entwicklungszyklus von einer Idee bis zum Produktivdeployment durchlaufen haben, erscheinen die zugehörigen Daten hier."],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["Die Review-Phase stellt die Zeit vom Anlegen eines Merge Requests bis zum Mergen dar. Sobald Sie Ihren ersten Merge Request abschließen, werden dessen Daten hier automatisch angezeigt."],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["Die Staging-Phase stellt die Zeit zwischen Mergen eines Merge Requests und dem Produktivdeployment dar. Sobald Sie das erste Produktivdeployment durchgeführt haben, werden dessen Daten hier automatisch angezeigt."],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["Die Test-Phase stellt die Zeit dar, die GitLab CI benötigt um die Pipelines von Merge Requests abzuarbeiten. Sobald die erste Pipeline abgeschlossen ist, werden deren Daten hier automatisch angezeigt."],"The time taken by each data entry gathered by that stage.":["Zeit die für das jeweilige Ereignis in der Phase ermittelt wurde."],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["Der mittlere aller erfassten Werte. Zum Beispiel ist für 3, 5, 9 der Median 5. Bei 3, 5, 7, 8 ist der Median (5+7)/2 = 6."],"Time before an issue gets scheduled":["Zeit bis ein Issue geplant wird"],"Time before an issue starts implementation":["Zeit bis die Implementierung für ein Issue beginnt"],"Time between merge request creation and merge/close":["Zeit zwischen Anlegen und Mergen/Schließen eines Merge Requests"],"Time until first merge request":["Zeit bis zum ersten Merge Request"],"Time|hr":["h","h"],"Time|min":["min","min"],"Time|s":["s"],"Total Time":["Gesamtzeit"],"Total test time for all commits/merges":["Gesamte Testlaufzeit für alle Commits/Merges"],"Want to see the data? Please ask an administrator for access.":["Um diese Daten einsehen zu können, wenden Sie sich bitte an Ihren Administrator."],"We don't have enough data to show this stage.":["Es liegen nicht genügend Daten vor, um diese Phase anzuzeigen."],"You need permission.":["Sie benötigen Zugriffsrechte."],"day":["Tag","Tage"]}}};
\ No newline at end of file
diff --git a/app/assets/javascripts/locale/en/app.js b/app/assets/javascripts/locale/en/app.js
deleted file mode 100644
index 0bb76c80b7a73de248ff67bed56b564146de8b8e..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/locale/en/app.js
+++ /dev/null
@@ -1 +0,0 @@
-var locales = locales || {}; locales['en'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","PO-Revision-Date":"2017-04-12 22:36-0500","Last-Translator":"FULL NAME <EMAIL@ADDRESS>","Language-Team":"English","Language":"en","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Plural-Forms":"nplurals=2; plural=n != 1;","lang":"en","domain":"app","plural_forms":"nplurals=2; plural=n != 1;"},"Are you sure you want to delete this pipeline schedule?":[""],"ByAuthor|by":[""],"Cancel":[""],"Commit":["",""],"Cron Timezone":[""],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":[""],"CycleAnalyticsStage|Code":[""],"CycleAnalyticsStage|Issue":[""],"CycleAnalyticsStage|Plan":[""],"CycleAnalyticsStage|Production":[""],"CycleAnalyticsStage|Review":[""],"CycleAnalyticsStage|Staging":[""],"CycleAnalyticsStage|Test":[""],"Delete":[""],"Deploy":["",""],"Description":[""],"Edit":[""],"Edit Pipeline Schedule %{id}":[""],"Failed to change the owner":[""],"Failed to remove the pipeline schedule":[""],"Filter":[""],"FirstPushedBy|First":[""],"FirstPushedBy|pushed by":[""],"From issue creation until deploy to production":[""],"From merge request merge until deploy to production":[""],"Interval Pattern":[""],"Introducing Cycle Analytics":[""],"Last %d day":["",""],"Last Pipeline":[""],"Limited to showing %d event at most":["",""],"Median":[""],"New Issue":["",""],"New Pipeline Schedule":[""],"No schedules":[""],"Not available":[""],"Not enough data":[""],"OpenedNDaysAgo|Opened":[""],"Owner":[""],"Pipeline Health":[""],"Pipeline Schedule":[""],"Pipeline Schedules":[""],"PipelineSchedules|Activated":[""],"PipelineSchedules|Active":[""],"PipelineSchedules|All":[""],"PipelineSchedules|Inactive":[""],"PipelineSchedules|Next Run":[""],"PipelineSchedules|None":[""],"PipelineSchedules|Provide a short description for this pipeline":[""],"PipelineSchedules|Take ownership":[""],"PipelineSchedules|Target":[""],"ProjectLifecycle|Stage":[""],"Read more":[""],"Related Commits":[""],"Related Deployed Jobs":[""],"Related Issues":[""],"Related Jobs":[""],"Related Merge Requests":[""],"Related Merged Requests":[""],"Save pipeline schedule":[""],"Schedule a new pipeline":[""],"Select a timezone":[""],"Select target branch":[""],"Showing %d event":["",""],"Target Branch":[""],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":[""],"The collection of events added to the data gathered for that stage.":[""],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":[""],"The phase of the development lifecycle.":[""],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":[""],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":[""],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":[""],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":[""],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":[""],"The time taken by each data entry gathered by that stage.":[""],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":[""],"Time before an issue gets scheduled":[""],"Time before an issue starts implementation":[""],"Time between merge request creation and merge/close":[""],"Time until first merge request":[""],"Time|hr":["",""],"Time|min":["",""],"Time|s":[""],"Total Time":[""],"Total test time for all commits/merges":[""],"Want to see the data? Please ask an administrator for access.":[""],"We don't have enough data to show this stage.":[""],"You need permission.":[""],"day":["",""]}}};
\ No newline at end of file
diff --git a/app/assets/javascripts/locale/es/app.js b/app/assets/javascripts/locale/es/app.js
deleted file mode 100644
index 6977625f4d8971eb30a7cde8ca88628ac85c786e..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/locale/es/app.js
+++ /dev/null
@@ -1 +0,0 @@
-var locales = locales || {}; locales['es'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","PO-Revision-Date":"2017-06-07 12:29-0500","Language-Team":"Spanish","Language":"es","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Plural-Forms":"nplurals=2; plural=n != 1;","Last-Translator":"Bob Van Landuyt <bob@gitlab.com>","X-Generator":"Poedit 2.0.2","lang":"es","domain":"app","plural_forms":"nplurals=2; plural=n != 1;"},"About auto deploy":["Acerca del auto despliegue"],"Activity":["Actividad"],"Add Changelog":["Agregar Changelog"],"Add Contribution guide":["Agregar guía de contribución"],"Add License":["Agregar Licencia"],"Add an SSH key to your profile to pull or push via SSH.":["Agregar una clave SSH a tu perfil para actualizar o enviar a través de SSH."],"Add new directory":["Agregar nuevo directorio"],"Archived project! Repository is read-only":["¡Proyecto archivado! El repositorio es de sólo lectura"],"Branch":["Rama","Ramas"],"Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}":["La rama <strong>%{branch_name}</strong> fue creada. Para configurar el auto despliegue, escoge una plantilla Yaml para GitLab CI y envía tus cambios. %{link_to_autodeploy_doc}"],"Branches":["Ramas"],"ByAuthor|by":["por"],"CI configuration":["Configuración de CI"],"Changelog":["Changelog"],"Charts":["Gráficos"],"CiStatusLabel|canceled":["cancelado"],"CiStatusLabel|created":["creado"],"CiStatusLabel|failed":["fallado"],"CiStatusLabel|manual action":["acción manual"],"CiStatusLabel|passed":["pasó"],"CiStatusLabel|passed with warnings":["pasó con advertencias"],"CiStatusLabel|pending":["pendiente"],"CiStatusLabel|skipped":["omitido"],"CiStatusLabel|waiting for manual action":["esperando acción manual"],"CiStatusText|blocked":["bloqueado"],"CiStatusText|canceled":["cancelado"],"CiStatusText|created":["creado"],"CiStatusText|failed":["fallado"],"CiStatusText|manual":["manual"],"CiStatusText|passed":["pasó"],"CiStatusText|pending":["pendiente"],"CiStatusText|skipped":["omitido"],"CiStatus|running":["en ejecución"],"Commit":["Cambio","Cambios"],"CommitMessage|Add %{file_name}":["Agregar %{file_name}"],"Commits":["Cambios"],"Commits|History":["Historial"],"Compare":["Comparar"],"Contribution guide":["Guía de contribución"],"Contributors":["Contribuidores"],"Copy URL to clipboard":["Copiar URL al portapapeles"],"Copy commit SHA to clipboard":["Copiar SHA del cambio al portapapeles"],"Create New Directory":["Crear Nuevo Directorio"],"Create directory":["Crear directorio"],"Create empty bare repository":["Crear repositorio vacío"],"Create merge request":["Crear solicitud de fusión"],"CreateNewFork|Fork":["Bifurcar"],"Custom notification events":["Eventos de notificaciones personalizadas"],"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 %{notification_link}.":["Los niveles de notificación personalizados son los mismos que los niveles participantes. Con los niveles de notificación personalizados, también recibirá notificaciones para eventos seleccionados. Para obtener más información, consulte %{notification_link}."],"Cycle Analytics":["Cycle Analytics"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["Cycle Analytics ofrece una visión general de cuánto tiempo tarda en pasar de idea a producción en su proyecto."],"CycleAnalyticsStage|Code":["Código"],"CycleAnalyticsStage|Issue":["Incidencia"],"CycleAnalyticsStage|Plan":["Planificación"],"CycleAnalyticsStage|Production":["Producción"],"CycleAnalyticsStage|Review":["Revisión"],"CycleAnalyticsStage|Staging":["Puesta en escena"],"CycleAnalyticsStage|Test":["Pruebas"],"Deploy":["Despliegue","Despliegues"],"Directory name":["Nombre del directorio"],"Don't show again":["No mostrar de nuevo"],"Download tar":["Descargar tar"],"Download tar.bz2":["Descargar tar.bz2"],"Download tar.gz":["Descargar tar.gz"],"Download zip":["Descargar zip"],"DownloadArtifacts|Download":["Descargar"],"DownloadSource|Download":["Descargar"],"Files":["Archivos"],"Find by path":["Buscar por ruta"],"Find file":["Buscar archivo"],"FirstPushedBy|First":["Primer"],"FirstPushedBy|pushed by":["enviado por"],"ForkedFromProjectPath|Forked from":["Bifurcado de"],"Forks":["Bifurcaciones"],"From issue creation until deploy to production":["Desde la creación de la incidencia hasta el despliegue a producción"],"From merge request merge until deploy to production":["Desde la integración de la solicitud de fusión hasta el despliegue a producción"],"Go to your fork":["Ir a tu bifurcación"],"GoToYourFork|Fork":["Bifurcación"],"Home":["Inicio"],"Housekeeping successfully started":["Servicio de limpieza iniciado con éxito"],"Import repository":["Importar repositorio"],"Introducing Cycle Analytics":["Introducción a Cycle Analytics"],"LFSStatus|Disabled":["Deshabilitado"],"LFSStatus|Enabled":["Habilitado"],"Last %d day":["Último %d día","Últimos %d días"],"Last Update":["Última actualización"],"Last commit":["Último cambio"],"Leave group":["Abandonar grupo"],"Leave project":["Abandonar proyecto"],"Limited to showing %d event at most":["Limitado a mostrar máximo %d evento","Limitado a mostrar máximo %d eventos"],"Median":["Mediana"],"MissingSSHKeyWarningLink|add an SSH key":["agregar una clave SSH"],"New Issue":["Nueva incidencia","Nuevas incidencias"],"New branch":["Nueva rama"],"New directory":["Nuevo directorio"],"New file":["Nuevo archivo"],"New issue":["Nueva incidencia"],"New merge request":["Nueva solicitud de fusión"],"New snippet":["Nuevo fragmento de código"],"New tag":["Nueva etiqueta"],"No repository":["No hay repositorio"],"Not available":["No disponible"],"Not enough data":["No hay suficientes datos"],"Notification events":["Eventos de notificación"],"NotificationEvent|Close issue":["Cerrar incidencia"],"NotificationEvent|Close merge request":["Cerrar solicitud de fusión"],"NotificationEvent|Failed pipeline":["Pipeline fallido"],"NotificationEvent|Merge merge request":["Integrar solicitud de fusión"],"NotificationEvent|New issue":["Nueva incidencia"],"NotificationEvent|New merge request":["Nueva solicitud de fusión"],"NotificationEvent|New note":["Nueva nota"],"NotificationEvent|Reassign issue":["Reasignar incidencia"],"NotificationEvent|Reassign merge request":["Reasignar solicitud de fusión"],"NotificationEvent|Reopen issue":["Reabrir incidencia"],"NotificationEvent|Successful pipeline":["Pipeline exitoso"],"NotificationLevel|Custom":["Personalizado"],"NotificationLevel|Disabled":["Deshabilitado"],"NotificationLevel|Global":["Global"],"NotificationLevel|On mention":["Cuando me mencionan"],"NotificationLevel|Participate":["Participación"],"NotificationLevel|Watch":["Vigilancia"],"OpenedNDaysAgo|Opened":["Abierto"],"Pipeline Health":["Estado del Pipeline"],"Project '%{project_name}' queued for deletion.":["Proyecto ‘%{project_name}’ en cola para eliminación."],"Project '%{project_name}' was successfully created.":["Proyecto ‘%{project_name}’ fue creado satisfactoriamente."],"Project '%{project_name}' was successfully updated.":["Proyecto ‘%{project_name}’ fue actualizado satisfactoriamente."],"Project '%{project_name}' will be deleted.":["Proyecto ‘%{project_name}’ será eliminado."],"Project access must be granted explicitly to each user.":["El acceso al proyecto debe concederse explícitamente a cada usuario."],"Project export could not be deleted.":["No se pudo eliminar la exportación del proyecto."],"Project export has been deleted.":["La exportación del proyecto ha sido eliminada."],"Project export link has expired. Please generate a new export from your project settings.":["El enlace de exportación del proyecto ha caducado. Por favor, genera una nueva exportación desde la configuración del proyecto."],"Project export started. A download link will be sent by email.":["Se inició la exportación del proyecto. Se enviará un enlace de descarga por correo electrónico."],"Project home":["Inicio del proyecto"],"ProjectFeature|Disabled":["Deshabilitada"],"ProjectFeature|Everyone with access":["Todos con acceso"],"ProjectFeature|Only team members":["Solo miembros del equipo"],"ProjectFileTree|Name":["Nombre"],"ProjectLastActivity|Never":["Nunca"],"ProjectLifecycle|Stage":["Etapa"],"ProjectNetworkGraph|Graph":["Historial gráfico"],"Read more":["Leer más"],"Readme":["Readme"],"RefSwitcher|Branches":["Ramas"],"RefSwitcher|Tags":["Etiquetas"],"Related Commits":["Cambios Relacionados"],"Related Deployed Jobs":["Trabajos Desplegados Relacionados"],"Related Issues":["Incidencias Relacionadas"],"Related Jobs":["Trabajos Relacionados"],"Related Merge Requests":["Solicitudes de fusión Relacionadas"],"Related Merged Requests":["Solicitudes de fusión Relacionadas"],"Remind later":["Recordar después"],"Remove project":["Eliminar proyecto"],"Request Access":["Solicitar acceso"],"Search branches and tags":["Buscar ramas y etiquetas"],"Select Archive Format":["Seleccionar formato de archivo"],"Set a password on your account to pull or push via %{protocol}":["Establezca una contraseña en su cuenta para actualizar o enviar a través de% {protocol}"],"Set up CI":["Configurar CI"],"Set up Koding":["Configurar Koding"],"Set up auto deploy":["Configurar auto despliegue"],"SetPasswordToCloneLink|set a password":["establecer una contraseña"],"Showing %d event":["Mostrando %d evento","Mostrando %d eventos"],"Source code":["Código fuente"],"StarProject|Star":["Destacar"],"Switch branch/tag":["Cambiar rama/etiqueta"],"Tag":["Etiqueta","Etiquetas"],"Tags":["Etiquetas"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["La etapa de desarrollo muestra el tiempo desde el primer cambio hasta la creación de la solicitud de fusión. Los datos serán automáticamente incorporados aquí una vez creada tu primera solicitud de fusión."],"The collection of events added to the data gathered for that stage.":["La colección de eventos agregados a los datos recopilados para esa etapa."],"The fork relationship has been removed.":["La relación con la bifurcación se ha eliminado."],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["La etapa de incidencia muestra el tiempo que toma desde la creación de un tema hasta asignar el tema a un hito, o añadir el tema a una lista en el panel de temas. Empieza a crear temas para ver los datos de esta etapa."],"The phase of the development lifecycle.":["La etapa del ciclo de vida de desarrollo."],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["La etapa de planificación muestra el tiempo desde el paso anterior hasta el envío de tu primera confirmación. Este tiempo se añadirá automáticamente una vez que usted envíe el primer cambio."],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["La etapa de producción muestra el tiempo total que tarda entre la creación de una incidencia y el despliegue del código a producción. Los datos se añadirán automáticamente una vez haya finalizado por completo el ciclo de idea a producción."],"The project can be accessed by any logged in user.":["El proyecto puede ser accedido por cualquier usuario conectado."],"The project can be accessed without any authentication.":["El proyecto puede accederse sin ninguna autenticación."],"The repository for this project does not exist.":["El repositorio para este proyecto no existe."],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["La etapa de revisión muestra el tiempo desde la creación de la solicitud de fusión hasta que los cambios se fusionaron. Los datos se añadirán automáticamente después de fusionar su primera solicitud de fusión."],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["La etapa de puesta en escena muestra el tiempo entre la fusión y el despliegue de código en el entorno de producción. Los datos se añadirán automáticamente una vez que se despliega a producción por primera vez."],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["La etapa de pruebas muestra el tiempo que GitLab CI toma para ejecutar cada pipeline para la solicitud de fusión relacionada. Los datos se añadirán automáticamente luego de que el primer pipeline termine de ejecutarse."],"The time taken by each data entry gathered by that stage.":["El tiempo utilizado por cada entrada de datos obtenido por esa etapa."],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["El valor en el punto medio de una serie de valores observados. Por ejemplo, entre 3, 5, 9, la mediana es 5. Entre 3, 5, 7, 8, la mediana es (5 + 7) / 2 = 6."],"This means you can not push code until you create an empty repository or import existing one.":["Esto significa que no puede enviar código hasta que cree un repositorio vacío o importe uno existente."],"Time before an issue gets scheduled":["Tiempo antes de que una incidencia sea programada"],"Time before an issue starts implementation":["Tiempo antes de que empieze la implementación de una incidencia"],"Time between merge request creation and merge/close":["Tiempo entre la creación de la solicitud de fusión y la integración o cierre de ésta"],"Time until first merge request":["Tiempo hasta la primera solicitud de fusión"],"Timeago|%s days ago":["hace %s días"],"Timeago|%s days remaining":["%s días restantes"],"Timeago|%s hours remaining":["%s horas restantes"],"Timeago|%s minutes ago":["hace %s minutos"],"Timeago|%s minutes remaining":["%s minutos restantes"],"Timeago|%s months ago":["hace %s meses"],"Timeago|%s months remaining":["%s meses restantes"],"Timeago|%s seconds remaining":["%s segundos restantes"],"Timeago|%s weeks ago":["hace %s semanas"],"Timeago|%s weeks remaining":["%s semanas restantes"],"Timeago|%s years ago":["hace %s años"],"Timeago|%s years remaining":["%s años restantes"],"Timeago|1 day remaining":["1 día restante"],"Timeago|1 hour remaining":["1 hora restante"],"Timeago|1 minute remaining":["1 minuto restante"],"Timeago|1 month remaining":["1 mes restante"],"Timeago|1 week remaining":["1 semana restante"],"Timeago|1 year remaining":["1 año restante"],"Timeago|Past due":["Atrasado"],"Timeago|a day ago":["hace un día"],"Timeago|a month ago":["hace 1 mes"],"Timeago|a week ago":["hace 1 semana"],"Timeago|a while":["hace un momento"],"Timeago|a year ago":["hace 1 año"],"Timeago|about %s hours ago":["hace alrededor de %s horas"],"Timeago|about a minute ago":["hace alrededor de 1 minuto"],"Timeago|about an hour ago":["hace alrededor de 1 hora"],"Timeago|in %s days":["en %s días"],"Timeago|in %s hours":["en %s horas"],"Timeago|in %s minutes":["en %s minutos"],"Timeago|in %s months":["en %s meses"],"Timeago|in %s seconds":["en %s segundos"],"Timeago|in %s weeks":["en %s semanas"],"Timeago|in %s years":["en %s años"],"Timeago|in 1 day":["en 1 día"],"Timeago|in 1 hour":["en 1 hora"],"Timeago|in 1 minute":["en 1 minuto"],"Timeago|in 1 month":["en 1 mes"],"Timeago|in 1 week":["en 1 semana"],"Timeago|in 1 year":["en 1 año"],"Timeago|less than a minute ago":["hace menos de 1 minuto"],"Time|hr":["hr","hrs"],"Time|min":["min","mins"],"Time|s":["s"],"Total Time":["Tiempo Total"],"Total test time for all commits/merges":["Tiempo total de pruebas para todos los cambios o integraciones"],"Unstar":["No Destacar"],"Upload New File":["Subir nuevo archivo"],"Upload file":["Subir archivo"],"Use your global notification setting":["Utiliza tu configuración de notificación global"],"VisibilityLevel|Internal":["Interno"],"VisibilityLevel|Private":["Privado"],"VisibilityLevel|Public":["Público"],"Want to see the data? Please ask an administrator for access.":["¿Quieres ver los datos? Por favor pide acceso al administrador."],"We don't have enough data to show this stage.":["No hay suficientes datos para mostrar en esta etapa."],"Withdraw Access Request":["Retirar Solicitud de Acceso"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["Va a eliminar %{project_name_with_namespace}.\\n¡El proyecto eliminado NO puede ser restaurado!\\n¿Estás TOTALMENTE seguro?"],"You are going to remove the fork relationship to source project %{forked_from_project}.  Are you ABSOLUTELY sure?":["Vas a eliminar el enlace de la bifurcación con el proyecto original %{forked_from_project}. ¿Estás TOTALMENTE seguro?"],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["Vas a transferir %{project_name_with_namespace} a otro propietario. ¿Estás TOTALMENTE seguro?"],"You can only add files when you are on a branch":["Sólo puede agregar archivos cuando estas en una rama"],"You must sign in to star a project":["Debes iniciar sesión para destacar un proyecto"],"You need permission.":["Necesitas permisos."],"You will not get any notifications via email":["No recibirás ninguna notificación por correo electrónico"],"You will only receive notifications for the events you choose":["Solo recibirás notificaciones de los eventos que elijas"],"You will only receive notifications for threads you have participated in":["Solo recibirás notificaciones de los temas en los que has participado"],"You will receive notifications for any activity":["Recibirás notificaciones para cualquier actividad"],"You will receive notifications only for comments in which you were @mentioned":["Recibirás notificaciones sólo para los comentarios en los que se te mencionó"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["No podrás actualizar o enviar código al proyecto a través de %{protocol} hasta que %{set_password_link} en tu cuenta"],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":["No podrás actualizar o enviar código al proyecto a través de SSH hasta que %{add_ssh_key_link} en su perfil"],"Your name":["Tu nombre"],"committed":["cambió"],"day":["día","días"],"notification emails":["correos electrónicos de notificación"]}}};
\ No newline at end of file
diff --git a/app/assets/javascripts/locale/fr/app.js b/app/assets/javascripts/locale/fr/app.js
deleted file mode 100644
index f9904ea61ea929da69471bfc3daa9806cb2e9eff..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/locale/fr/app.js
+++ /dev/null
@@ -1 +0,0 @@
-var locales = locales || {}; locales['fr'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-15 20:38+0000","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-14 04:21-0400","Last-Translator":"Dremor <egeorget@opmbx.org>","Language-Team":"French (https://www.transifex.com/gitlab-fr/teams/75145/fr/)","Language":"fr","Plural-Forms":"nplurals=2; plural=(n > 1);","X-Generator":"Zanata 3.9.6","lang":"fr","domain":"app","plural_forms":"nplurals=2; plural=(n > 1);"},"ByAuthor|by":["par"],"Commit":["Validation","Validations"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["L’analyseur de cycle permet d’avoir une vue d’ensemble du temps nécessaire pour aller d’une idée à sa mise en production pour votre projet."],"CycleAnalyticsStage|Code":["Code"],"CycleAnalyticsStage|Issue":["Incident"],"CycleAnalyticsStage|Plan":["Planification"],"CycleAnalyticsStage|Production":["Production"],"CycleAnalyticsStage|Review":["Examen"],"CycleAnalyticsStage|Staging":["Pré-production"],"CycleAnalyticsStage|Test":["Test"],"Deploy":["Déploiement","Déploiements"],"FirstPushedBy|First":["En premier"],"FirstPushedBy|pushed by":["poussé par"],"From issue creation until deploy to production":["Depuis la création de l'incident jusqu'au déploiement en production"],"From merge request merge until deploy to production":["Depuis la fusion de la demande de fusion jusqu'au déploiement en production"],"Introducing Cycle Analytics":["Introduction à l'analyseur de cycle"],"Last %d day":["Le dernier %d jour","Les derniers %d jours"],"Limited to showing %d event at most":["Limiter l'affichage au plus à %d évènement","Limiter l'affichage au plus à %d évènements"],"Median":["Médian"],"New Issue":["Nouvel incident","Nouveaux incidents"],"Not available":["Indisponible"],"Not enough data":["Données insuffisantes"],"OpenedNDaysAgo|Opened":["Ouvert"],"Pipeline Health":["Santé du Pipeline"],"ProjectLifecycle|Stage":["Étape"],"Read more":["Lire plus"],"Related Commits":["Validations liés"],"Related Deployed Jobs":["Tâches de déploiement liés"],"Related Issues":["Incidents liés"],"Related Jobs":["Tâches liées"],"Related Merge Requests":["Demandes de fusion liées"],"Related Merged Requests":["Demandes fusionnées liées"],"Showing %d event":["Affichage de %d évènement","Affichage de %d évènements"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["L’étape de développement montre le temps entre la première validation et la création de la demande de fusion. Les données seront automatiquement ajoutées ici une fois que vous aurez créé votre première demande de fusion."],"The collection of events added to the data gathered for that stage.":["L’ensemble d’évènements ajoutés aux données récupérées pour cette étape."],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["L'étape des incidents montre le temps nécessaire entre la création d'un incident et son assignation à un jalon, ou son ajout à une liste d'un tableau d'incident. Débutez à créer des incidents pour voir des données pour cette étape."],"The phase of the development lifecycle.":["Les étapes du cycle de développement."],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["L’étape de planification montre le temps entre l’étape précédente et l’envoi de votre première validation. Ce temps sera automatiquement ajouté quand vous pousserez votre première validation."],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["L’étape de mise en production montre le temps nécessaire entre la création d’un incident et le déploiement du code en production. Les données seront automatiquement ajoutées une fois que vous aurez complété le cycle complet, depuis l’idée jusqu’à la mise en production."],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["L’étape d’évaluation montre le temps entre la création de la demande de fusion et la fusion effective de celle-ci. Ces données seront automatiquement ajoutées après que vous ayez fusionné votre première demande de fusion."],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["L’étape de pré-production indique le temps entre la fusion de la RF et le déploiement du code dans l’environnent de production. Les données seront automatiquement ajoutées une fois que vous déploierez en production pour la première fois."],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["L’étape de test montre le temps que le CI de GitLab met pour exécuter chaque pipeline liés à la demande de fusion. Les données seront automatiquement ajoutées après que votre premier pipeline s’achèvera."],"The time taken by each data entry gathered by that stage.":["Le temps pris par chaque entrée récoltée durant cette étape."],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["La valeur située au point médian d’une série de valeur observée. C.à.d., entre 3, 5, 9, le médian est 5. Entre 3, 5, 7, 8, le médian est (5+7)/2 = 6."],"Time before an issue gets scheduled":["Temps avant qu’un incident ne soit planifié"],"Time before an issue starts implementation":["Temps avant que résolution ne débute"],"Time between merge request creation and merge/close":["Temps entre la création d'une demande de fusion et sa fusion/clôture"],"Time until first merge request":["Temps jusqu’à la première demande de fusion"],"Time|hr":["hr","hrs"],"Time|min":["min","mins"],"Time|s":["s"],"Total Time":["Temps total"],"Total test time for all commits/merges":["Temps total de test pour toutes les validations/fusions"],"Want to see the data? Please ask an administrator for access.":["Vous voulez voir les données ? Merci de contacter un administrateur pour en obtenir l’accès."],"We don't have enough data to show this stage.":["Nous n'avons pas suffisamment de données pour afficher cette étape."],"You need permission.":["Vous avez besoin d’une autorisation."],"day":["jour","jours"],"%{commit_author_link} committed %{commit_timeago}":["%{commit_author_link} a validé %{commit_timeago}"],"About auto deploy":["A propos de l'auto-déploiement"],"Active":["Actif"],"Activity":["Activité"],"Add Changelog":["Ajouter un journal des modifications"],"Add Contribution guide":["Ajouter un guide de contribution"],"Add License":["Ajouter une licence"],"Add an SSH key to your profile to pull or push via SSH.":["Ajoutez une clef SSH à votre profil pour pouvoir récupérer et pousser par SSH."],"Add new directory":["Ajouter un nouveau dossier"],"Archived project! Repository is read-only":["Projet archivé ! Le dépôt est en lecture seule"],"Are you sure you want to delete this pipeline schedule?":["Êtes-vous sûr de vouloir supprimer ce pipeline programmé"],"Attach a file by drag &amp; drop or %{upload_link}":["Attachez un fichier par glisser &amp; déposer ou %{upload_link}"],"Branch":["Branche","Branches"],"#~ \"Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, cho\"#~ \"ose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_do\"#~ \"c}\"":["#~ \"La branche <strong>%{branch_name}</strong> a été crée. Pour mettre en place le\"#~ \" déploiement automatisé, sélectionnez un modèle de fichier Yaml pour Gitlab CI\"#~ \", et validez les modifications. %{link_to_autodeploy_doc}\""],"Branches":["Branches"],"Browse files":["Parcourir les fichiers"],"CI configuration":["Configuration du CI"],"Cancel":["Annuler"],"ChangeTypeActionLabel|Pick into branch":["Sélectionner dans la branche"],"ChangeTypeActionLabel|Revert in branch":["Annuler dans la branche"],"ChangeTypeAction|Cherry-pick":["Sélectionner"],"ChangeType|commit":["validation"],"ChangeType|merge request":["demande de fusion"],"Changelog":["Journal des modifications"],"Charts":["Graphiques"],"Cherry-pick this commit":["Sélectionner cette validation"],"Cherry-pick this merge-request":["Sélectionner cette demande de fusion"],"CiStatusLabel|canceled":["annulé"],"CiStatusLabel|created":["créé"],"CiStatusLabel|failed":["échoué"],"CiStatusLabel|manual action":["action manuelle"],"CiStatusLabel|passed":["passé"],"CiStatusLabel|passed with warnings":["passé avec des avertissements"],"CiStatusLabel|pending":["en attente"],"CiStatusLabel|skipped":["ignoré"],"CiStatusLabel|waiting for manual action":["en attente d'action manuelle"],"CiStatusText|blocked":["bloqué"],"CiStatusText|canceled":["annulé "],"CiStatusText|created":["créé"],"CiStatusText|failed":["échoué"],"CiStatusText|manual":["manuel"],"CiStatusText|passed":["passé"],"CiStatusText|pending":["en attente"],"CiStatusText|skipped":["ignoré"],"CiStatus|running":["en cours"],"Commit message":["Message de validation"],"CommitMessage|Add %{file_name}":["Ajout de %{file_name}"],"Commits":["Validations"],"Commits|History":["Historique"],"Committed by":["Validé par"],"Compare":["Comparer"],"Contribution guide":["Guilde de contribution"],"Contributors":["Contributeurs"],"Copy URL to clipboard":["Copier l'URL dans le presse-papier"],"Copy commit SHA to clipboard":["Copier le SAH de la validation"],"Create New Directory":["Créer un nouveau dossier"],"Create directory":["Créer un dossier"],"Create empty bare repository":["Créer un dépôt vide"],"Create merge request":["Créer une demande de fusion"],"Create new...":["Créer nouveau..."],"CreateNewFork|Fork":["Fork"],"CreateTag|Tag":["Étiquette"],"Cron Timezone":["Fuseau horaire de Cron"],"Cron syntax":["Syntaxe CRON"],"Custom":["Personnalisé"],"Custom notification events":["Événements de notification personnalisés"],"#~ \"Custom notification levels are the same as participating levels. With custom n\"#~ \"otification levels you will also receive notifications for select events. To f\"#~ \"ind out more, check out %{notification_link}.\"":["#~ \"Le niveau de notification Personnalisé est similaire au niveau Participation. \"#~ \"Il permet cependant également de recevoir des notifications pour des événement\"#~ \"s sélectionnés. Pour plus d’information, vous pouvez consulter %{notification_\"#~ \"link}.\""],"Cycle Analytics":["Analyseur de cycle"],"Define a custom pattern with cron syntax":["Définir un schéma personnalisé avec une syntaxe CRON"],"Delete":["Supprimer"],"Description":["Description"],"Directory name":["Nom du dossier"],"Don't show again":["Ne plus montrer"],"Download":["Télécharger"],"Download tar":["Télécharger tar"],"Download tar.bz2":["Télécharger tar.bz2"],"Download tar.gz":["Télécharger tar.gz"],"Download zip":["Télécharger zip"],"DownloadArtifacts|Download":["Télécharger"],"DownloadCommit|Email Patches":["Patch email"],"DownloadCommit|Plain Diff":["Diff simple"],"DownloadSource|Download":["Télécharger"],"Edit":["Éditer"],"Edit Pipeline Schedule %{id}":["Éditer le pipeline programmé %{id}"],"Every day (at 4:00am)":["Chaque jour (à 4:00 du matin)"],"Every month (on the 1st at 4:00am)":["Chaque mois (le 1er à 4:00 du matin)"],"Every week (Sundays at 4:00am)":["Chaque semaine (Dimanche à 4:00 du matin)"],"Failed to change the owner":["Échec du changement de propriétaire"],"Failed to remove the pipeline schedule":["Échec de la suppression du pipeline  programmé"],"Files":["Fichiers"],"Find by path":["Rechercher par chemin"],"Find file":["Rechercher un fichier"],"Fork":["Fork","Forks"],"ForkedFromProjectPath|Forked from":["Forké depuis"],"Go to your fork":["Aller à votre fork"],"GoToYourFork|Fork":["Fork"],"Home":["Accueil"],"Housekeeping successfully started":["Maintenance démarrée avec succès"],"Import repository":["Importer un dépôt"],"Interval Pattern":["Schéma d’intervalle"],"LFSStatus|Disabled":["Désactivé"],"LFSStatus|Enabled":["Activé"],"Last Pipeline":["Dernier pipeline"],"Last Update":["Dernière mise à jour"],"Last commit":["Dernière validation"],"Learn more in the":["En apprendre plus dans le"],"Leave group":["Quitter le groupe"],"Leave project":["Quitter le projet"],"MissingSSHKeyWarningLink|add an SSH key":["ajouter un clef SSH"],"New Pipeline Schedule":["Nouveau pipeline programmé"],"New branch":["Nouvelle branche"],"New directory":["Nouveau dossier"],"New file":["Nouveau Fichier"],"New issue":["Nouvel incident"],"New merge request":["Nouvelle demande de fusion"],"New schedule":["Nouveau programme"],"New snippet":["Nouvel extrait de code"],"New tag":["Nouvelle étiquette"],"No repository":["Pas de dépôt"],"No schedules":["Aucun programme"],"Notification events":["Événement de notifications"],"NotificationEvent|Close issue":["Clore l'incident"],"NotificationEvent|Close merge request":["Clore la demande de fusion"],"NotificationEvent|Failed pipeline":["Pipeline échoué"],"NotificationEvent|Merge merge request":["Fusionner le demande de fusion"],"NotificationEvent|New issue":["Nouvel incident"],"NotificationEvent|New merge request":["Nouvelle demande de fusion"],"NotificationEvent|New note":["Nouvelle note"],"NotificationEvent|Reassign issue":["Réassigner l'incident"],"NotificationEvent|Reassign merge request":["Réassigner la demande de fusion"],"NotificationEvent|Reopen issue":["Ré-ouvrir l'incident"],"NotificationEvent|Successful pipeline":["Pipeline réussi"],"NotificationLevel|Custom":["Personnalisé"],"NotificationLevel|Disabled":["Désactivé"],"NotificationLevel|Global":["Global"],"NotificationLevel|On mention":["En cas de mention"],"NotificationLevel|Participate":["Participation"],"NotificationLevel|Watch":["Surveillé"],"OfSearchInADropdown|Filter":["Filtre"],"Options":["Options"],"Owner":["Propriétaire"],"Pipeline":["Pipeline"],"Pipeline Schedule":["Programmation de pipeline"],"Pipeline Schedules":["Programmations de pipeline"],"PipelineSchedules|Activated":["Activé"],"PipelineSchedules|Active":["Active"],"PipelineSchedules|All":["Tous"],"PipelineSchedules|Inactive":["Inactive"],"PipelineSchedules|Next Run":["Prochaine exécution"],"PipelineSchedules|None":["Aucune"],"PipelineSchedules|Provide a short description for this pipeline":["Indiquez une courte description"],"PipelineSchedules|Take ownership":["S’approprier"],"PipelineSchedules|Target":["Cible"],"Project '%{project_name}' queued for deletion.":["Projet '%{project_name}' en attente de suppression."],"Project '%{project_name}' was successfully created.":["Projet '%{project_name}' créé avec succès."],"Project '%{project_name}' was successfully updated.":["Projet '%{project_name}' mis à jour avec succès."],"Project '%{project_name}' will be deleted.":["Projet '%{project_name}' sera supprimé."],"Project access must be granted explicitly to each user.":["L’accès au projet doit être explicitement accordé à chaque utilisateur."],"Project export could not be deleted.":["L'export du projet n'a pas pu être supprimé."],"Project export has been deleted.":["L'export du projet a été supprimé."],"#~ \"Project export link has expired. Please generate a new export from your projec\"#~ \"t settings.\"":["#~ \"Le lien de l’export du projet a expiré. Merci de générer un nouvel export depu\"#~ \"is les paramètres du projet.\""],"Project export started. A download link will be sent by email.":["#~ \"L'export du projet a débuté. Un lien de téléchargement sera envoyé par courrie\"#~ \"l.\""],"Project home":["Accueil du projet"],"ProjectFeature|Disabled":["Désactivé"],"ProjectFeature|Everyone with access":["Toute personne ayant accès"],"ProjectFeature|Only team members":["Seulement les membres de l'équipe"],"ProjectFileTree|Name":["Nom"],"ProjectLastActivity|Never":["Jamais"],"ProjectNetworkGraph|Graph":["Graphique "],"Readme":["LisezMoi"],"RefSwitcher|Branches":["Branches"],"RefSwitcher|Tags":["Étiquettes"],"Remind later":["Me le rappeler ultérieurement"],"Remove project":["Supprimer le projet"],"Request Access":["Demander l'accès"],"Revert this commit":["Annuler cette validation"],"Revert this merge-request":["Annuler cette demande de fusion"],"Save pipeline schedule":["Sauvegarder le pipeline programmé"],"Schedule a new pipeline":["Programmer un nouveau pipeline"],"Scheduling Pipelines":["Programmer des pipelines"],"Search branches and tags":["Rechercher dans les branches et les étiquettes"],"Select Archive Format":["Sélectionnez le format de l'archive"],"Select a timezone":["Sélectionnez un fuseau horaire"],"Select target branch":["Sélectionnez une branche cible"],"Set a password on your account to pull or push via %{protocol}":["#~ \"Définissez un mot de passe pour votre compte pour pouvoir tirer ou pousser par\"#~ \"  %{protocol}\""],"Set up CI":["Mettre en place le CI"],"Set up Koding":["Mettre en place Koding"],"Set up auto deploy":["Mettre en place l’auto-déploiement "],"SetPasswordToCloneLink|set a password":["définir un mot de passe"],"Source code":["Code source"],"StarProject|Star":["S'abonner"],"Start a <strong>new merge request</strong> with these changes":["Créer une <strong>nouvelle demande de fusion</strong> avec ces changements"],"Switch branch/tag":["Changer de branche / d'étiquette"],"Tag":["Étiquette","Étiquettes"],"Tags":["Étiquettes"],"Target Branch":["Branche cible"],"The fork relationship has been removed.":["La relation de fork a été supprimée."],"#~ \"The pipelines schedule runs pipelines in the future, repeatedly, for specific \"#~ \"branches or tags. Those scheduled pipelines will inherit limited project acces\"#~ \"s based on their associated user.\"":["#~ \"Les pipelines programmés exécutent des pipelines dans le futur, de façon répét\"#~ \"ée, pour les branches et étiquettes spécifiées. Ces pipelines programmés hérit\"#~ \"ent d’un accès partiel au projet basé sur l’utilisateur que leurs est associé.\""],"The project can be accessed by any logged in user.":["Votre projet peut être accédé par n’importe quel utilisateur authentifié"],"The project can be accessed without any authentication.":["Votre projet peut être accédé sans aucune authentification."],"The repository for this project does not exist.":["Le dépôt pour ce projet n'existe pas."],"#~ \"This means you can not push code until you create an empty repository or impor\"#~ \"t existing one.\"":["#~ \"Cela signifie que vous ne pouvez pas pousser du code tant que vous ne créez pa\"#~ \"s un dépôt vide, ou importez une dépôt existant.\""],"Timeago|%s days ago":["Il y a %s jours"],"Timeago|%s days remaining":["Il reste %s jours"],"Timeago|%s hours remaining":["Il reste %s heures"],"Timeago|%s minutes ago":["Il y a %s minutes"],"Timeago|%s minutes remaining":["Il reste %s minutes"],"Timeago|%s months ago":["Il y a %s mois"],"Timeago|%s months remaining":["Il reste %s mois"],"Timeago|%s seconds remaining":["Il reste %s secondes"],"Timeago|%s weeks ago":["Il y a %s semaines"],"Timeago|%s weeks remaining":["Il reste %s semaines"],"Timeago|%s years ago":["Il y a %s ans"],"Timeago|%s years remaining":["Il reste %s ans"],"Timeago|1 day remaining":["Il reste un jour"],"Timeago|1 hour remaining":["Il reste une heure"],"Timeago|1 minute remaining":["Il reste une minute"],"Timeago|1 month remaining":["Il reste un mois"],"Timeago|1 week remaining":["Il reste une semaine"],"Timeago|1 year remaining":["Il reste un an"],"Timeago|Past due":["En retard"],"Timeago|a day ago":["Il y a un jour"],"Timeago|a month ago":["Il y a un mois"],"Timeago|a week ago":["Il y a une semaine"],"Timeago|a while":["Il y a un moment"],"Timeago|a year ago":["Il y a un an"],"Timeago|about %s hours ago":["Il y a environ %s heures"],"Timeago|about a minute ago":["Il y a environ une minute"],"Timeago|about an hour ago":["Il y a environ une heure"],"Timeago|in %s days":["Dans %s jours"],"Timeago|in %s hours":["Dans %s heures"],"Timeago|in %s minutes":["Dans %s minutes"],"Timeago|in %s months":["Dans %s mois"],"Timeago|in %s seconds":["Dans %s secondes"],"Timeago|in %s weeks":["Dans %s semaines"],"Timeago|in %s years":["Dans %s années"],"Timeago|in 1 day":["Dans 1 jour"],"Timeago|in 1 hour":["Dans 1 heure"],"Timeago|in 1 minute":["Dans 1 minute"],"Timeago|in 1 month":["Dans 1 mois"],"Timeago|in 1 week":["Dans 1  semaine"],"Timeago|in 1 year":["Dans 1  an"],"Timeago|less than a minute ago":["il y a moins d'une minute"],"Unstar":["Se désabonner"],"Upload New File":["Téléverser un nouveau fichier"],"Upload file":["Téléverser un fichier"],"Use your global notification setting":["Utiliser vos paramètres de notification globaux"],"VisibilityLevel|Internal":["Interne"],"VisibilityLevel|Private":["Privé"],"VisibilityLevel|Public":["Publique"],"Withdraw Access Request":["Retirer la demande d'accès"],"#~ \"You are going to remove %{project_name_with_namespace}.\\n\"#~ \"Removed project CANNOT be restored!\\n\"#~ \"Are you ABSOLUTELY sure?\"":["#~ \"Vous êtes sur le point de supprimer %{project_name_with_namespace}.\\n\"#~ \"Les projets supprimés NE PEUVENT PAS être restaurés !\\n\"#~ \"Êtes vous ABSOLUMENT sûr ?  \""],"#~ \"You are going to remove the fork relationship to source project %{forked_from_\"#~ \"project}. Are you ABSOLUTELY sure?\"":["#~ \"Vous allez supprimer la relation de fork avec le projet source %{forked_from_p\"#~ \"roject}. Êtes-vous VRAIMENT sûr.\""],"#~ \"You are going to transfer %{project_name_with_namespace} to another owner. Are\"#~ \" you ABSOLUTELY sure?\"":["#~ \"Vous allez transférer %{project_name_with_namespace} à un nouveau propriétaire\"#~ \". Êtes vous VRAIMENT sûr ?\""],"You can only add files when you are on a branch":["Vous ne pouvez ajouter de fichier que dans une branche"],"You must sign in to star a project":["Vous devez vous identifier pour vous abonner à un projet"],"You will not get any notifications via email":["Vous ne recevrez aucune notification par courriel"],"You will only receive notifications for the events you choose":["#~ \"Vous ne recevrez de notification que pour les évènements que vous aurez choisi\"#~ \"s\""],"You will only receive notifications for threads you have participated in":["#~ \"Vous ne recevrez de notification que pour les sujets auxquels vous avez partic\"#~ \"ipé\""],"You will receive notifications for any activity":["Vous recevrez des notifications pour n’importe quelles activités"],"You will receive notifications only for comments in which you were @mentioned":["#~ \"Vous ne recevrez de notifications que pour les commentaires où vous êtes @ment\"#~ \"ionné\""],"#~ \"You won't be able to pull or push project code via %{protocol} until you %{set\"#~ \"_password_link} on your account\"":["#~ \"Vous ne pourrez pas  récupérer ou pousser  de code par %{protocol} tant que vo\"#~ \"us n'aurez pas %{set_password_link} pour votre compte\""],"#~ \"You won't be able to pull or push project code via SSH until you %{add_ssh_key\"#~ \"_link} to your profile\"":["#~ \"Vous ne pourrez pas  récupérer ou pousser  de code par SSH tant que vous n’aur\"#~ \"ez pas %{add_ssh_key_link} dans votre profil\""],"Your name":["Votre nom"],"notification emails":["courriels de notification"],"parent":["parent","parents"],"pipeline schedules documentation":["documentation des pipeline programmés"],"with stage":["avec l'étape","avec les étapes"]}}};
\ No newline at end of file
diff --git a/app/assets/javascripts/locale/pt_BR/app.js b/app/assets/javascripts/locale/pt_BR/app.js
deleted file mode 100644
index f2eed3da064ccd00fac8f188692920e89cc960c0..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/locale/pt_BR/app.js
+++ /dev/null
@@ -1 +0,0 @@
-var locales = locales || {}; locales['pt_BR'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-05-04 19:24-0500","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-05 03:29-0400","Last-Translator":"Alexandre Alencar <alexandre.alencar@gmail.com>","Language-Team":"Portuguese (Brazil)","Language":"pt-BR","X-Generator":"Zanata 3.9.6","Plural-Forms":"nplurals=2; plural=(n != 1)","lang":"pt_BR","domain":"app","plural_forms":"nplurals=2; plural=(n != 1)"},"ByAuthor|by":["por"],"Commit":["Commit","Commits"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["A Análise de Ciclo fornece uma visão geral de quanto tempo uma ideia demora para ir para produção em seu projeto."],"CycleAnalyticsStage|Code":["Código"],"CycleAnalyticsStage|Issue":["Tarefa"],"CycleAnalyticsStage|Plan":["Plano"],"CycleAnalyticsStage|Production":["Produção"],"CycleAnalyticsStage|Review":["Revisão"],"CycleAnalyticsStage|Staging":["Homologação"],"CycleAnalyticsStage|Test":["Teste"],"Deploy":["Implantação","Implantações"],"FirstPushedBy|First":["Primeiro"],"FirstPushedBy|pushed by":["publicado por"],"From issue creation until deploy to production":["Da criação de tarefas até a implantação para a produção"],"From merge request merge until deploy to production":["Da incorporação do merge request  até a implantação em produção"],"Introducing Cycle Analytics":["Apresentando a Análise de Ciclo"],"Last %d day":["Último %d dia","Últimos %d dias"],"Limited to showing %d event at most":["Limitado a mostrar %d evento no máximo","Limitado a mostrar %d eventos no máximo"],"Median":["Mediana"],"New Issue":["Nova Tarefa","Novas Tarefas"],"Not available":["Não disponível"],"Not enough data":["Dados insuficientes"],"OpenedNDaysAgo|Opened":["Aberto"],"Pipeline Health":["Saúde da Pipeline"],"ProjectLifecycle|Stage":["Etapa"],"Read more":["Ler mais"],"Related Commits":["Commits Relacionados"],"Related Deployed Jobs":["Jobs Relacionados Incorporados"],"Related Issues":["Tarefas Relacionadas"],"Related Jobs":["Jobs Relacionados"],"Related Merge Requests":["Merge Requests Relacionados"],"Related Merged Requests":["Merge Requests Relacionados"],"Showing %d event":["Mostrando %d evento","Mostrando %d eventos"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["O estágio de codificação mostra o tempo desde o primeiro commit até a criação do merge request. \\nOs dados serão automaticamente adicionados aqui uma vez que você tenha criado seu primeiro merge request."],"The collection of events added to the data gathered for that stage.":["A coleção de eventos adicionados aos dados coletados para esse estágio."],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["O estágio em questão mostra o tempo que leva desde a criação de uma tarefa até a sua assinatura para um milestone, ou a sua adição para a lista no seu Painel de Tarefas. Comece a criar tarefas para ver dados para esta etapa."],"The phase of the development lifecycle.":["A fase do ciclo de vida do desenvolvimento."],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["A fase de planejamento mostra o tempo do passo anterior até empurrar o seu primeiro commit. Este tempo será adicionado automaticamente assim que você realizar seu primeiro commit."],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["O estágio de produção mostra o tempo total que leva entre criar uma tarefa e implantar o código na produção. Os dados serão adicionados automaticamente até que você complete todo o ciclo de produção."],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["A etapa de revisão mostra o tempo de criação de um merge request até que o merge seja feito. Os dados serão automaticamente adicionados depois que você fizer seu primeiro merge request."],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["O estágio de estágio mostra o tempo entre a fusão do MR e o código de implantação para o ambiente de produção. Os dados serão automaticamente adicionados depois de implantar na produção pela primeira vez."],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["A fase de teste mostra o tempo que o GitLab CI leva para executar cada pipeline para o merge request relacionado. Os dados serão automaticamente adicionados após a conclusão do primeiro pipeline."],"The time taken by each data entry gathered by that stage.":["O tempo necessário para cada entrada de dados reunida por essa etapa."],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["O valor situado no ponto médio de uma série de valores observados. Ex., entre 3, 5, 9, a mediana é 5. Entre 3, 5, 7, 8, a mediana é (5 + 7) / 2 = 6."],"Time before an issue gets scheduled":["Tempo até que uma tarefa seja planejada"],"Time before an issue starts implementation":["Tempo até que uma tarefa comece a ser implementada"],"Time between merge request creation and merge/close":["Tempo entre a criação do merge request e o merge/fechamento"],"Time until first merge request":["Tempo até o primeiro merge request"],"Time|hr":["h","hs"],"Time|min":["min","mins"],"Time|s":["s"],"Total Time":["Tempo Total"],"Total test time for all commits/merges":["Tempo de teste total para todos os commits/merges"],"Want to see the data? Please ask an administrator for access.":["Precisa visualizar os dados? Solicite acesso ao administrador."],"We don't have enough data to show this stage.":["Não temos dados suficientes para mostrar esta fase."],"You need permission.":["Você precisa de permissão."],"day":["dia","dias"]}}};
\ No newline at end of file
diff --git a/app/assets/javascripts/locale/zh_CN/app.js b/app/assets/javascripts/locale/zh_CN/app.js
deleted file mode 100644
index d1335cfbc0fd557440f7c925a16116853e0b1561..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/locale/zh_CN/app.js
+++ /dev/null
@@ -1 +0,0 @@
-var locales = locales || {}; locales['zh_CN'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","PO-Revision-Date":"2017-05-04 19:24-0500","Last-Translator":"HuangTao <htve@outlook.com>, 2017","Language-Team":"Chinese (China) (https://www.transifex.com/gitlab-zh/teams/75177/zh_CN/)","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Language":"zh_CN","Plural-Forms":"nplurals=1; plural=0;","lang":"zh_CN","domain":"app","plural_forms":"nplurals=1; plural=0;"},"Are you sure you want to delete this pipeline schedule?":[""],"ByAuthor|by":["作者:"],"Cancel":[""],"Commit":["提交"],"Cron Timezone":[""],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["周期分析概述了项目从想法到产品实现的各阶段所需的时间。"],"CycleAnalyticsStage|Code":["编码"],"CycleAnalyticsStage|Issue":["议题"],"CycleAnalyticsStage|Plan":["计划"],"CycleAnalyticsStage|Production":["生产"],"CycleAnalyticsStage|Review":["评审"],"CycleAnalyticsStage|Staging":["预发布"],"CycleAnalyticsStage|Test":["测试"],"Delete":[""],"Deploy":["部署"],"Description":[""],"Edit":[""],"Edit Pipeline Schedule %{id}":[""],"Failed to change the owner":[""],"Failed to remove the pipeline schedule":[""],"Filter":[""],"FirstPushedBy|First":["首次推送"],"FirstPushedBy|pushed by":["推送者:"],"From issue creation until deploy to production":["从创建议题到部署至生产环境"],"From merge request merge until deploy to production":["从合并请求被合并后到部署至生产环境"],"Interval Pattern":[""],"Introducing Cycle Analytics":["周期分析简介"],"Last %d day":["最后 %d 天"],"Last Pipeline":[""],"Limited to showing %d event at most":["最多显示 %d 个事件"],"Median":["中位数"],"New Issue":["新议题"],"New Pipeline Schedule":[""],"No schedules":[""],"Not available":["数据不足"],"Not enough data":["数据不足"],"OpenedNDaysAgo|Opened":["开始于"],"Owner":[""],"Pipeline Health":["流水线健康指标"],"Pipeline Schedule":[""],"Pipeline Schedules":[""],"PipelineSchedules|Activated":[""],"PipelineSchedules|Active":[""],"PipelineSchedules|All":[""],"PipelineSchedules|Inactive":[""],"PipelineSchedules|Next Run":[""],"PipelineSchedules|None":[""],"PipelineSchedules|Provide a short description for this pipeline":[""],"PipelineSchedules|Take ownership":[""],"PipelineSchedules|Target":[""],"ProjectLifecycle|Stage":["项目生命周期"],"Read more":["了解更多"],"Related Commits":["相关的提交"],"Related Deployed Jobs":["相关的部署作业"],"Related Issues":["相关的议题"],"Related Jobs":["相关的作业"],"Related Merge Requests":["相关的合并请求"],"Related Merged Requests":["相关已合并的合并请求"],"Save pipeline schedule":[""],"Schedule a new pipeline":[""],"Select a timezone":[""],"Select target branch":[""],"Showing %d event":["显示 %d 个事件"],"Target Branch":[""],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["编码阶段概述了从第一次提交到创建合并请求的时间。创建第一个合并请求后,数据将自动添加到此处。"],"The collection of events added to the data gathered for that stage.":["与该阶段相关的事件。"],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["议题阶段概述了从创建议题到将议题设置里程碑或将议题添加到议题看板的时间。开始创建议题以查看此阶段的数据。"],"The phase of the development lifecycle.":["项目生命周期中的各个阶段。"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["计划阶段概述了从议题添加到日程后到推送首次提交的时间。当首次推送提交后,数据将自动添加到此处。"],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["生产阶段概述了从创建一个议题到将代码部署到生产环境的总时间。当完成想法到部署生产的循环,数据将自动添加到此处。"],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["评审阶段概述了从创建合并请求到被合并的时间。当创建第一个合并请求后,数据将自动添加到此处。"],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["预发布阶段概述了从合并请求被合并到部署至生产环境的总时间。首次部署到生产环境后,数据将自动添加到此处。"],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["测试阶段概述了GitLab CI为相关合并请求运行每个流水线所需的时间。当第一个流水线运行完成后,数据将自动添加到此处。"],"The time taken by each data entry gathered by that stage.":["该阶段每条数据所花的时间"],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["中位数是一个数列中最中间的值。例如在 3、5、9 之间,中位数是 5。在 3、5、7、8 之间,中位数是 (5 + 7)/ 2 = 6。"],"Time before an issue gets scheduled":["议题被列入日程表的时间"],"Time before an issue starts implementation":["开始进行编码前的时间"],"Time between merge request creation and merge/close":["从创建合并请求到被合并或关闭的时间"],"Time until first merge request":["创建第一个合并请求之前的时间"],"Time|hr":["小时"],"Time|min":["分钟"],"Time|s":["秒"],"Total Time":["总时间"],"Total test time for all commits/merges":["所有提交和合并的总测试时间"],"Want to see the data? Please ask an administrator for access.":["权限不足。如需查看相关数据,请向管理员申请权限。"],"We don't have enough data to show this stage.":["该阶段的数据不足,无法显示。"],"You need permission.":["您需要相关的权限。"],"day":["天"]}}};
\ No newline at end of file
diff --git a/app/assets/javascripts/locale/zh_HK/app.js b/app/assets/javascripts/locale/zh_HK/app.js
deleted file mode 100644
index 30cb1e6b89e87322f9e43ab6c35f3e40dd4efd87..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/locale/zh_HK/app.js
+++ /dev/null
@@ -1 +0,0 @@
-var locales = locales || {}; locales['zh_HK'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","PO-Revision-Date":"2017-05-04 19:24-0500","Last-Translator":"HuangTao <htve@outlook.com>, 2017","Language-Team":"Chinese (Hong Kong) (https://www.transifex.com/gitlab-zh/teams/75177/zh_HK/)","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Language":"zh_HK","Plural-Forms":"nplurals=1; plural=0;","lang":"zh_HK","domain":"app","plural_forms":"nplurals=1; plural=0;"},"Are you sure you want to delete this pipeline schedule?":[""],"ByAuthor|by":["作者:"],"Cancel":[""],"Commit":["提交"],"Cron Timezone":[""],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["週期分析概述了項目從想法到產品實現的各階段所需的時間。"],"CycleAnalyticsStage|Code":["編碼"],"CycleAnalyticsStage|Issue":["議題"],"CycleAnalyticsStage|Plan":["計劃"],"CycleAnalyticsStage|Production":["生產"],"CycleAnalyticsStage|Review":["評審"],"CycleAnalyticsStage|Staging":["預發布"],"CycleAnalyticsStage|Test":["測試"],"Delete":[""],"Deploy":["部署"],"Description":[""],"Edit":[""],"Edit Pipeline Schedule %{id}":[""],"Failed to change the owner":[""],"Failed to remove the pipeline schedule":[""],"Filter":[""],"FirstPushedBy|First":["首次推送"],"FirstPushedBy|pushed by":["推送者:"],"From issue creation until deploy to production":["從創建議題到部署到生產環境"],"From merge request merge until deploy to production":["從合併請求的合併到部署至生產環境"],"Interval Pattern":[""],"Introducing Cycle Analytics":["週期分析簡介"],"Last %d day":["最後 %d 天"],"Last Pipeline":[""],"Limited to showing %d event at most":["最多顯示 %d 個事件"],"Median":["中位數"],"New Issue":["新議題"],"New Pipeline Schedule":[""],"No schedules":[""],"Not available":["不可用"],"Not enough data":["數據不足"],"OpenedNDaysAgo|Opened":["開始於"],"Owner":[""],"Pipeline Health":["流水線健康指標"],"Pipeline Schedule":[""],"Pipeline Schedules":[""],"PipelineSchedules|Activated":[""],"PipelineSchedules|Active":[""],"PipelineSchedules|All":[""],"PipelineSchedules|Inactive":[""],"PipelineSchedules|Next Run":[""],"PipelineSchedules|None":[""],"PipelineSchedules|Provide a short description for this pipeline":[""],"PipelineSchedules|Take ownership":[""],"PipelineSchedules|Target":[""],"ProjectLifecycle|Stage":["項目生命週期"],"Read more":["了解更多"],"Related Commits":["相關的提交"],"Related Deployed Jobs":["相關的部署作業"],"Related Issues":["相關的議題"],"Related Jobs":["相關的作業"],"Related Merge Requests":["相關的合併請求"],"Related Merged Requests":["相關已合併的合並請求"],"Save pipeline schedule":[""],"Schedule a new pipeline":[""],"Select a timezone":[""],"Select target branch":[""],"Showing %d event":["顯示 %d 個事件"],"Target Branch":[""],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["編碼階段概述了從第一次提交到創建合併請求的時間。創建第壹個合並請求後,數據將自動添加到此處。"],"The collection of events added to the data gathered for that stage.":["與該階段相關的事件。"],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["議題階段概述了從創建議題到將議題設置裏程碑或將議題添加到議題看板的時間。創建一個議題後,數據將自動添加到此處。"],"The phase of the development lifecycle.":["項目生命週期中的各個階段。"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["計劃階段概述了從議題添加到日程後到推送首次提交的時間。當首次推送提交後,數據將自動添加到此處。"],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["生產階段概述了從創建議題到將代碼部署到生產環境的時間。當完成完整的想法到部署生產,數據將自動添加到此處。"],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["評審階段概述了從創建合並請求到合併的時間。當創建第壹個合並請求後,數據將自動添加到此處。"],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["預發布階段概述了合並請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。"],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["測試階段概述了GitLab CI為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。"],"The time taken by each data entry gathered by that stage.":["該階段每條數據所花的時間"],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["中位數是一個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。"],"Time before an issue gets scheduled":["議題被列入日程表的時間"],"Time before an issue starts implementation":["開始進行編碼前的時間"],"Time between merge request creation and merge/close":["從創建合併請求到被合並或關閉的時間"],"Time until first merge request":["創建第壹個合併請求之前的時間"],"Time|hr":["小時"],"Time|min":["分鐘"],"Time|s":["秒"],"Total Time":["總時間"],"Total test time for all commits/merges":["所有提交和合併的總測試時間"],"Want to see the data? Please ask an administrator for access.":["權限不足。如需查看相關數據,請向管理員申請權限。"],"We don't have enough data to show this stage.":["該階段的數據不足,無法顯示。"],"You need permission.":["您需要相關的權限。"],"day":["天"]}}};
\ No newline at end of file
diff --git a/app/assets/javascripts/locale/zh_TW/app.js b/app/assets/javascripts/locale/zh_TW/app.js
deleted file mode 100644
index f0fe1e31f18a92e434a2a6d3dfea8d57c2c185a9..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/locale/zh_TW/app.js
+++ /dev/null
@@ -1 +0,0 @@
-var locales = locales || {}; locales['zh_TW'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","PO-Revision-Date":"2017-05-04 19:24-0500","Last-Translator":"HuangTao <htve@outlook.com>, 2017","Language-Team":"Chinese (Taiwan) (https://www.transifex.com/gitlab-zh/teams/75177/zh_TW/)","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Language":"zh_TW","Plural-Forms":"nplurals=1; plural=0;","lang":"zh_TW","domain":"app","plural_forms":"nplurals=1; plural=0;"},"Are you sure you want to delete this pipeline schedule?":[""],"ByAuthor|by":["作者:"],"Cancel":[""],"Commit":["送交"],"Cron Timezone":[""],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["週期分析概述了你的專案從想法到產品實現,各階段所需的時間。"],"CycleAnalyticsStage|Code":["程式開發"],"CycleAnalyticsStage|Issue":["議題"],"CycleAnalyticsStage|Plan":["計劃"],"CycleAnalyticsStage|Production":["上線"],"CycleAnalyticsStage|Review":["複閱"],"CycleAnalyticsStage|Staging":["預備"],"CycleAnalyticsStage|Test":["測試"],"Delete":[""],"Deploy":["部署"],"Description":[""],"Edit":[""],"Edit Pipeline Schedule %{id}":[""],"Failed to change the owner":[""],"Failed to remove the pipeline schedule":[""],"Filter":[""],"FirstPushedBy|First":["首次推送"],"FirstPushedBy|pushed by":["推送者:"],"From issue creation until deploy to production":["從議題建立至線上部署"],"From merge request merge until deploy to production":["從請求被合併後至線上部署"],"Interval Pattern":[""],"Introducing Cycle Analytics":["週期分析簡介"],"Last %d day":["最後 %d 天"],"Last Pipeline":[""],"Limited to showing %d event at most":["最多顯示 %d 個事件"],"Median":["中位數"],"New Issue":["新議題"],"New Pipeline Schedule":[""],"No schedules":[""],"Not available":["無法使用"],"Not enough data":["資料不足"],"OpenedNDaysAgo|Opened":["開始於"],"Owner":[""],"Pipeline Health":["流水線健康指標"],"Pipeline Schedule":[""],"Pipeline Schedules":[""],"PipelineSchedules|Activated":[""],"PipelineSchedules|Active":[""],"PipelineSchedules|All":[""],"PipelineSchedules|Inactive":[""],"PipelineSchedules|Next Run":[""],"PipelineSchedules|None":[""],"PipelineSchedules|Provide a short description for this pipeline":[""],"PipelineSchedules|Take ownership":[""],"PipelineSchedules|Target":[""],"ProjectLifecycle|Stage":["專案生命週期"],"Read more":["了解更多"],"Related Commits":["相關的送交"],"Related Deployed Jobs":["相關的部署作業"],"Related Issues":["相關的議題"],"Related Jobs":["相關的作業"],"Related Merge Requests":["相關的合併請求"],"Related Merged Requests":["相關已合併的請求"],"Save pipeline schedule":[""],"Schedule a new pipeline":[""],"Select a timezone":[""],"Select target branch":[""],"Showing %d event":["顯示 %d 個事件"],"Target Branch":[""],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["程式開發階段顯示從第一次送交到建立合併請求的時間。建立第一個合併請求後,資料將自動填入。"],"The collection of events added to the data gathered for that stage.":["與該階段相關的事件。"],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["議題階段顯示從議題建立到設置里程碑、或將該議題加至議題看板的時間。建立第一個議題後,資料將自動填入。"],"The phase of the development lifecycle.":["專案開發生命週期的各個階段。"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["計劃階段顯示從議題添加到日程後至推送第一個送交的時間。當第一次推送送交後,資料將自動填入。"],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["上線階段顯示從建立一個議題到部署程式至線上的總時間。當完成從想法到產品實現的循環後,資料將自動填入。"],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["複閱階段顯示從合併請求建立後至被合併的時間。當建立第一個合併請求後,資料將自動填入。"],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["預備階段顯示從合併請求被合併後至部署上線的時間。當第一次部署上線後,資料將自動填入。"],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["測試階段顯示相關合併請求的流水線所花的時間。當第一個流水線運作完畢後,資料將自動填入。"],"The time taken by each data entry gathered by that stage.":["每筆該階段相關資料所花的時間。"],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["中位數是一個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。"],"Time before an issue gets scheduled":["議題被列入日程表的時間"],"Time before an issue starts implementation":["議題等待開始實作的時間"],"Time between merge request creation and merge/close":["合併請求被合併或是關閉的時間"],"Time until first merge request":["第一個合併請求被建立前的時間"],"Time|hr":["小時"],"Time|min":["分鐘"],"Time|s":["秒"],"Total Time":["總時間"],"Total test time for all commits/merges":["所有送交和合併的總測試時間"],"Want to see the data? Please ask an administrator for access.":["權限不足。如需查看相關資料,請向管理員申請權限。"],"We don't have enough data to show this stage.":["因該階段的資料不足而無法顯示相關資訊"],"You need permission.":["您需要相關的權限。"],"day":["天"]}}};
\ No newline at end of file
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index ed7629948ca2fce5e6c34bbecf35b9089b56abea..fe752d95b903c255b38895fc8793d32976719f63 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -70,7 +70,7 @@ import './ajax_loading_spinner';
 import './api';
 import './aside';
 import './autosave';
-import AwardsHandler from './awards_handler';
+import loadAwardsHandler from './awards_handler';
 import './breakpoints';
 import './broadcast_message';
 import './build';
@@ -299,9 +299,10 @@ $(function () {
   // Commit show suppressed diff
   });
   $('.navbar-toggle').on('click', function () {
-    $('.header-content .title').toggle();
+    $('.header-content .title, .header-content .navbar-sub-nav').toggle();
     $('.header-content .header-logo').toggle();
     $('.header-content .navbar-collapse').toggle();
+    $('.js-navbar-toggle-left, .js-navbar-toggle-right, .title-container').toggle();
     return $('.navbar-toggle').toggleClass('active');
   });
   // Show/hide comments on diff
@@ -354,10 +355,10 @@ $(function () {
   $window.off('resize.app').on('resize.app', function () {
     return fitSidebarForSize();
   });
-  gl.awardsHandler = new AwardsHandler();
+  loadAwardsHandler();
   new Aside();
 
-  gl.utils.initTimeagoTimeout();
+  gl.utils.renderTimeago();
 
   $(document).trigger('init.scrolling-tabs');
 });
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index 894ed81b044c5a989222881a414579ee82ce61a7..7840f05a8aefd5ac25009012014c84ecb0b74ee6 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -144,7 +144,9 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion';
         this.resetViewContainer();
         this.mountPipelinesView();
       } else {
-        this.expandView();
+        if (Breakpoints.get().getBreakpointSize() !== 'xs') {
+          this.expandView();
+        }
         this.resetViewContainer();
         this.destroyPipelinesView();
       }
@@ -155,7 +157,10 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion';
 
     scrollToElement(container) {
       if (location.hash) {
-        const offset = -$('.js-tabs-affix').outerHeight();
+        const offset = 0 - (
+          $('.navbar-gitlab').outerHeight() +
+          $('.js-tabs-affix').outerHeight()
+        );
         const $el = $(`${container} ${location.hash}:not(.match)`);
         if ($el.length) {
           $.scrollTo($el[0], { offset });
@@ -165,9 +170,8 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion';
 
     // Activate a tab based on the current action
     activateTab(action) {
-      const activate = action === 'show' ? 'notes' : action;
       // important note: the .tab('show') method triggers 'shown.bs.tab' event itself
-      $(`.merge-request-tabs a[data-action='${activate}']`).tab('show');
+      $(`.merge-request-tabs a[data-action='${action}']`).tab('show');
     }
 
     // Replaces the current Merge Request-specific action in the URL with a new one
@@ -182,7 +186,7 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion';
     //   location.pathname # => "/namespace/project/merge_requests/1/diffs"
     //
     //   location.pathname # => "/namespace/project/merge_requests/1/diffs"
-    //   setCurrentAction('notes')
+    //   setCurrentAction('show')
     //   location.pathname # => "/namespace/project/merge_requests/1"
     //
     //   location.pathname # => "/namespace/project/merge_requests/1/diffs"
@@ -191,13 +195,13 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion';
     //
     // Returns the new URL String
     setCurrentAction(action) {
-      this.currentAction = action === 'show' ? 'notes' : action;
+      this.currentAction = action;
 
-      // Remove a trailing '/commits' '/diffs' '/pipelines' '/new' '/new/diffs'
-      let newState = location.pathname.replace(/\/(commits|diffs|pipelines|new|new\/diffs)(\.html)?\/?$/, '');
+      // Remove a trailing '/commits' '/diffs' '/pipelines'
+      let newState = location.pathname.replace(/\/(commits|diffs|pipelines)(\.html)?\/?$/, '');
 
       // Append the new action if we're on a tab other than 'notes'
-      if (this.currentAction !== 'notes') {
+      if (this.currentAction !== 'show' && this.currentAction !== 'new') {
         newState += `/${this.currentAction}`;
       }
 
@@ -233,11 +237,18 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion';
     }
 
     mountPipelinesView() {
-      this.commitPipelinesTable = new gl.CommitPipelinesTable().$mount();
+      const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view');
+      const CommitPipelinesTable = gl.CommitPipelinesTable;
+      this.commitPipelinesTable = new CommitPipelinesTable({
+        propsData: {
+          endpoint: pipelineTableViewEl.dataset.endpoint,
+          helpPagePath: pipelineTableViewEl.dataset.helpPagePath,
+        },
+      }).$mount();
+
       // $mount(el) replaces the el with the new rendered component. We need it in order to mount
       // it everytime this tab is clicked - https://vuejs.org/v2/api/#vm-mount
-      document.querySelector('#commit-pipeline-table-view')
-        .appendChild(this.commitPipelinesTable.$el);
+      pipelineTableViewEl.appendChild(this.commitPipelinesTable.$el);
     }
 
     loadDiff(source) {
@@ -284,7 +295,7 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion';
           // Scroll any linked note into view
           // Similar to `toggler_behavior` in the discussion tab
           const hash = window.gl.utils.getLocationHash();
-          const anchor = hash && $container.find(`[id="${hash}"]`);
+          const anchor = hash && $container.find(`.note[id="${hash}"]`);
           if (anchor && anchor.length > 0) {
             const notesContent = anchor.closest('.notes_content');
             const lineType = notesContent.hasClass('new') ? 'new' : 'old';
@@ -294,6 +305,7 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion';
               forceShow: true,
             });
             anchor[0].scrollIntoView();
+            window.gl.utils.handleLocationHash();
             // We have multiple elements on the page with `#note_xxx`
             // (discussion and diff tabs) and `:target` only applies to the first
             anchor.addClass('target');
diff --git a/app/assets/javascripts/milestone.js b/app/assets/javascripts/milestone.js
index 07ede5ee91358d716343b25f34f5892312ec5aef..3e07ec4d0aa01e7301c5b6611670efafda45d00c 100644
--- a/app/assets/javascripts/milestone.js
+++ b/app/assets/javascripts/milestone.js
@@ -4,87 +4,7 @@
 
 (function() {
   this.Milestone = (function() {
-    Milestone.updateIssue = function(li, issue_url, data) {
-      return $.ajax({
-        type: "PUT",
-        url: issue_url,
-        data: data,
-        success: function(_data) {
-          return Milestone.successCallback(_data, li);
-        },
-        error: function(data) {
-          return new Flash("Issue update failed", 'alert');
-        },
-        dataType: "json"
-      });
-    };
-
-    Milestone.sortIssues = function(url, data) {
-      return $.ajax({
-        type: "PUT",
-        url,
-        data: data,
-        success: function(_data) {
-          return Milestone.successCallback(_data);
-        },
-        error: function() {
-          return new Flash("Issues update failed", 'alert');
-        },
-        dataType: "json"
-      });
-    };
-
-    Milestone.sortMergeRequests = function(url, data) {
-      return $.ajax({
-        type: "PUT",
-        url,
-        data: data,
-        success: function(_data) {
-          return Milestone.successCallback(_data);
-        },
-        error: function(data) {
-          return new Flash("Issue update failed", 'alert');
-        },
-        dataType: "json"
-      });
-    };
-
-    Milestone.updateMergeRequest = function(li, merge_request_url, data) {
-      return $.ajax({
-        type: "PUT",
-        url: merge_request_url,
-        data: data,
-        success: function(_data) {
-          return Milestone.successCallback(_data, li);
-        },
-        error: function(data) {
-          return new Flash("Issue update failed", 'alert');
-        },
-        dataType: "json"
-      });
-    };
-
-    Milestone.successCallback = function(data, element) {
-      const $avatarContainer = $(element).find('.assignee-icon');
-      $avatarContainer.empty();
-
-      if (data.assignees && data.assignees.length > 0) {
-        const $avatars = data.assignees.map((assignee) => {
-          const img_tag = $('<img/>');
-          img_tag.attr('src', assignee.avatar_url);
-          img_tag.addClass('avatar s16');
-          return img_tag;
-        });
-
-        $avatarContainer.append($avatars);
-      }
-    };
-
     function Milestone() {
-      this.issuesSortEndpoint = $('#tab-issues').data('sort-endpoint');
-      this.mergeRequestsSortEndpoint = $('#tab-merge-requests').data('sort-endpoint');
-
-      this.bindIssuesSorting();
       this.bindTabsSwitching();
 
       // Load merge request tab if it is active
@@ -94,22 +14,6 @@
       this.loadInitialTab();
     }
 
-    Milestone.prototype.bindIssuesSorting = function() {
-      if (!this.issuesSortEndpoint) return;
-
-      $('#issues-list-unassigned, #issues-list-ongoing, #issues-list-closed').each(function (i, el) {
-        this.createSortable(el, {
-          group: 'issue-list',
-          listEls: $('.issues-sortable-list'),
-          fieldName: 'issue',
-          sortCallback: (data) => {
-            Milestone.sortIssues(this.issuesSortEndpoint, data);
-          },
-          updateCallback: Milestone.updateIssue,
-        });
-      }.bind(this));
-    };
-
     Milestone.prototype.bindTabsSwitching = function() {
       return $('a[data-toggle="tab"]').on('show.bs.tab', (e) => {
         const $target = $(e.target);
@@ -119,69 +23,6 @@
       });
     };
 
-    Milestone.prototype.bindMergeRequestSorting = function() {
-      if (!this.mergeRequestsSortEndpoint) return;
-
-      $("#merge_requests-list-unassigned, #merge_requests-list-ongoing, #merge_requests-list-closed").each(function (i, el) {
-        this.createSortable(el, {
-          group: 'merge-request-list',
-          listEls: $(".merge_requests-sortable-list:not(#merge_requests-list-merged)"),
-          fieldName: 'merge_request',
-          sortCallback: (data) => {
-            Milestone.sortMergeRequests(this.mergeRequestsSortEndpoint, data);
-          },
-          updateCallback: Milestone.updateMergeRequest,
-        });
-      }.bind(this));
-    };
-
-    Milestone.prototype.createSortable = function(el, opts) {
-      return Sortable.create(el, {
-        group: opts.group,
-        filter: '.is-disabled',
-        forceFallback: true,
-        onStart: function(e) {
-          opts.listEls.css('min-height', e.item.offsetHeight);
-        },
-        onEnd: function () {
-          opts.listEls.css("min-height", "0px");
-        },
-        onUpdate: function(e) {
-          var ids = this.toArray(),
-            data;
-
-          if (ids.length) {
-            data = ids.map(function(id) {
-              return 'sortable_' + opts.fieldName + '[]=' + id;
-            }).join('&');
-
-            opts.sortCallback(data);
-          }
-        },
-        onAdd: function (e) {
-          var data, issuableId, issuableUrl, newState;
-          newState = e.to.dataset.state;
-          issuableUrl = e.item.dataset.url;
-          data = (function() {
-            switch (newState) {
-              case 'ongoing':
-                return `${opts.fieldName}[assignee_ids][]=${gon.current_user_id}`;
-              case 'unassigned':
-                return `${opts.fieldName}[assignee_ids][]=0`;
-              case 'closed':
-                return opts.fieldName + '[state_event]=close';
-            }
-          })();
-          if (e.from.dataset.state === 'closed') {
-            data += '&' + opts.fieldName + '[state_event]=reopen';
-          }
-
-          opts.updateCallback(e.item, issuableUrl, data);
-          this.options.onUpdate.call(this, e);
-        }
-      });
-    };
-
     Milestone.prototype.loadInitialTab = function() {
       const $target = $(`.js-milestone-tabs a[href="${location.hash}"]`);
 
@@ -203,10 +44,6 @@
         .done((data) => {
           $(tabElId).html(data.html);
           $target.addClass('is-loaded');
-
-          if (tabElId === '#tab-merge-requests') {
-            this.bindMergeRequestSorting();
-          }
         });
       }
     };
diff --git a/app/assets/javascripts/monitoring/components/monitoring.vue b/app/assets/javascripts/monitoring/components/monitoring.vue
new file mode 100644
index 0000000000000000000000000000000000000000..a6a2d3119e3e7acc0fa6d4b859b3ec9f50ebe4e1
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/monitoring.vue
@@ -0,0 +1,157 @@
+<script>
+  /* global Flash */
+  import _ from 'underscore';
+  import statusCodes from '../../lib/utils/http_status';
+  import MonitoringService from '../services/monitoring_service';
+  import monitoringRow from './monitoring_row.vue';
+  import monitoringState from './monitoring_state.vue';
+  import MonitoringStore from '../stores/monitoring_store';
+  import eventHub from '../event_hub';
+
+  export default {
+
+    data() {
+      const metricsData = document.querySelector('#prometheus-graphs').dataset;
+      const store = new MonitoringStore();
+
+      return {
+        store,
+        state: 'gettingStarted',
+        hasMetrics: gl.utils.convertPermissionToBoolean(metricsData.hasMetrics),
+        documentationPath: metricsData.documentationPath,
+        settingsPath: metricsData.settingsPath,
+        endpoint: metricsData.additionalMetrics,
+        deploymentEndpoint: metricsData.deploymentEndpoint,
+        showEmptyState: true,
+        backOffRequestCounter: 0,
+        updateAspectRatio: false,
+        updatedAspectRatios: 0,
+        resizeThrottled: {},
+      };
+    },
+
+    components: {
+      monitoringRow,
+      monitoringState,
+    },
+
+    methods: {
+      getGraphsData() {
+        const maxNumberOfRequests = 3;
+        this.state = 'loading';
+        gl.utils.backOff((next, stop) => {
+          this.service.get().then((resp) => {
+            if (resp.status === statusCodes.NO_CONTENT) {
+              this.backOffRequestCounter = this.backOffRequestCounter += 1;
+              if (this.backOffRequestCounter < maxNumberOfRequests) {
+                next();
+              } else {
+                stop(new Error('Failed to connect to the prometheus server'));
+              }
+            } else {
+              stop(resp);
+            }
+          }).catch(stop);
+        })
+        .then((resp) => {
+          if (resp.status === statusCodes.NO_CONTENT) {
+            this.state = 'unableToConnect';
+            return false;
+          }
+          return resp.json();
+        })
+        .then((metricGroupsData) => {
+          if (!metricGroupsData) return false;
+          this.store.storeMetrics(metricGroupsData.data);
+          return this.getDeploymentData();
+        })
+        .then((deploymentData) => {
+          if (deploymentData !== false) {
+            this.store.storeDeploymentData(deploymentData.deployments);
+            this.showEmptyState = false;
+          }
+          return {};
+        })
+        .catch(() => {
+          this.state = 'unableToConnect';
+        });
+      },
+
+      getDeploymentData() {
+        return this.service.getDeploymentData(this.deploymentEndpoint)
+          .then(resp => resp.json())
+          .catch(() => new Flash('Error getting deployment information.'));
+      },
+
+      resize() {
+        this.updateAspectRatio = true;
+      },
+
+      toggleAspectRatio() {
+        this.updatedAspectRatios = this.updatedAspectRatios += 1;
+        if (this.store.getMetricsCount() === this.updatedAspectRatios) {
+          this.updateAspectRatio = !this.updateAspectRatio;
+          this.updatedAspectRatios = 0;
+        }
+      },
+
+    },
+
+    created() {
+      this.service = new MonitoringService(this.endpoint);
+      eventHub.$on('toggleAspectRatio', this.toggleAspectRatio);
+    },
+
+    beforeDestroy() {
+      eventHub.$off('toggleAspectRatio', this.toggleAspectRatio);
+      window.removeEventListener('resize', this.resizeThrottled, false);
+    },
+
+    mounted() {
+      this.resizeThrottled = _.throttle(this.resize, 600);
+      if (!this.hasMetrics) {
+        this.state = 'gettingStarted';
+      } else {
+        this.getGraphsData();
+        window.addEventListener('resize', this.resizeThrottled, false);
+      }
+    },
+  };
+</script>
+<template>
+  <div 
+    class="prometheus-graphs" 
+    v-if="!showEmptyState">
+    <div 
+      class="row"
+      v-for="(groupData, index) in store.groups"
+      :key="index">
+      <div 
+        class="col-md-12">
+        <div 
+          class="panel panel-default prometheus-panel">
+          <div 
+            class="panel-heading">
+            <h4>{{groupData.group}}</h4>
+          </div>
+          <div 
+            class="panel-body">
+            <monitoring-row
+              v-for="(row, index) in groupData.metrics" 
+              :key="index"
+              :row-data="row"
+              :update-aspect-ratio="updateAspectRatio"
+              :deployment-data="store.deploymentData"
+            />
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+  <monitoring-state 
+    :selected-state="state"
+    :documentation-path="documentationPath"
+    :settings-path="settingsPath"
+    v-else
+  />
+</template>
diff --git a/app/assets/javascripts/monitoring/components/monitoring_column.vue b/app/assets/javascripts/monitoring/components/monitoring_column.vue
new file mode 100644
index 0000000000000000000000000000000000000000..0f33581ec52856af8a66c383b5d6ff77e69110ef
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/monitoring_column.vue
@@ -0,0 +1,293 @@
+<script>
+  /* global Breakpoints */
+  import d3 from 'd3';
+  import monitoringLegends from './monitoring_legends.vue';
+  import monitoringFlag from './monitoring_flag.vue';
+  import monitoringDeployment from './monitoring_deployment.vue';
+  import MonitoringMixin from '../mixins/monitoring_mixins';
+  import eventHub from '../event_hub';
+  import measurements from '../utils/measurements';
+  import { formatRelevantDigits } from '../../lib/utils/number_utils';
+
+  const bisectDate = d3.bisector(d => d.time).left;
+
+  export default {
+    props: {
+      columnData: {
+        type: Object,
+        required: true,
+      },
+      classType: {
+        type: String,
+        required: true,
+      },
+      updateAspectRatio: {
+        type: Boolean,
+        required: true,
+      },
+      deploymentData: {
+        type: Array,
+        required: true,
+      },
+    },
+
+    mixins: [MonitoringMixin],
+
+    data() {
+      return {
+        graphHeight: 450,
+        graphWidth: 600,
+        graphHeightOffset: 120,
+        xScale: {},
+        yScale: {},
+        margin: {},
+        data: [],
+        breakpointHandler: Breakpoints.get(),
+        unitOfDisplay: '',
+        areaColorRgb: '#8fbce8',
+        lineColorRgb: '#1f78d1',
+        yAxisLabel: '',
+        legendTitle: '',
+        reducedDeploymentData: [],
+        area: '',
+        line: '',
+        measurements: measurements.large,
+        currentData: {
+          time: new Date(),
+          value: 0,
+        },
+        currentYCoordinate: 0,
+        currentXCoordinate: 0,
+        currentFlagPosition: 0,
+        metricUsage: '',
+        showFlag: false,
+        showDeployInfo: true,
+      };
+    },
+
+    components: {
+      monitoringLegends,
+      monitoringFlag,
+      monitoringDeployment,
+    },
+
+    computed: {
+      outterViewBox() {
+        return `0 0 ${this.graphWidth} ${this.graphHeight}`;
+      },
+
+      innerViewBox() {
+        if ((this.graphWidth - 150) > 0) {
+          return `0 0 ${this.graphWidth - 150} ${this.graphHeight}`;
+        }
+        return '0 0 0 0';
+      },
+
+      axisTransform() {
+        return `translate(70, ${this.graphHeight - 100})`;
+      },
+
+      paddingBottomRootSvg() {
+        return {
+          paddingBottom: `${(Math.ceil(this.graphHeight * 100) / this.graphWidth) || 0}%`,
+        };
+      },
+    },
+
+    methods: {
+      draw() {
+        const breakpointSize = this.breakpointHandler.getBreakpointSize();
+        const query = this.columnData.queries[0];
+        this.margin = measurements.large.margin;
+        if (breakpointSize === 'xs' || breakpointSize === 'sm') {
+          this.graphHeight = 300;
+          this.margin = measurements.small.margin;
+          this.measurements = measurements.small;
+        }
+        this.data = query.result[0].values;
+        this.unitOfDisplay = query.unit || 'N/A';
+        this.yAxisLabel = this.columnData.y_label || 'Values';
+        this.legendTitle = query.legend || 'Average';
+        this.graphWidth = this.$refs.baseSvg.clientWidth -
+                     this.margin.left - this.margin.right;
+        this.graphHeight = this.graphHeight - this.margin.top - this.margin.bottom;
+        if (this.data !== undefined) {
+          this.renderAxesPaths();
+          this.formatDeployments();
+        }
+      },
+
+      handleMouseOverGraph(e) {
+        let point = this.$refs.graphData.createSVGPoint();
+        point.x = e.clientX;
+        point.y = e.clientY;
+        point = point.matrixTransform(this.$refs.graphData.getScreenCTM().inverse());
+        point.x = point.x += 7;
+        const timeValueOverlay = this.xScale.invert(point.x);
+        const overlayIndex = bisectDate(this.data, timeValueOverlay, 1);
+        const d0 = this.data[overlayIndex - 1];
+        const d1 = this.data[overlayIndex];
+        if (d0 === undefined || d1 === undefined) return;
+        const evalTime = timeValueOverlay - d0[0] > d1[0] - timeValueOverlay;
+        this.currentData = evalTime ? d1 : d0;
+        this.currentXCoordinate = Math.floor(this.xScale(this.currentData.time));
+        const currentDeployXPos = this.mouseOverDeployInfo(point.x);
+        this.currentYCoordinate = this.yScale(this.currentData.value);
+
+        if (this.currentXCoordinate > (this.graphWidth - 200)) {
+          this.currentFlagPosition = this.currentXCoordinate - 103;
+        } else {
+          this.currentFlagPosition = this.currentXCoordinate;
+        }
+
+        if (currentDeployXPos) {
+          this.showFlag = false;
+        } else {
+          this.showFlag = true;
+        }
+
+        this.metricUsage = `${formatRelevantDigits(this.currentData.value)} ${this.unitOfDisplay}`;
+      },
+
+      renderAxesPaths() {
+        const axisXScale = d3.time.scale()
+          .range([0, this.graphWidth]);
+        this.yScale = d3.scale.linear()
+          .range([this.graphHeight - this.graphHeightOffset, 0]);
+        axisXScale.domain(d3.extent(this.data, d => d.time));
+        this.yScale.domain([0, d3.max(this.data.map(d => d.value))]);
+
+        const xAxis = d3.svg.axis()
+          .scale(axisXScale)
+          .ticks(measurements.ticks)
+          .orient('bottom');
+
+        const yAxis = d3.svg.axis()
+          .scale(this.yScale)
+          .ticks(measurements.ticks)
+          .orient('left');
+
+        d3.select(this.$refs.baseSvg).select('.x-axis').call(xAxis);
+
+        const width = this.graphWidth;
+        d3.select(this.$refs.baseSvg).select('.y-axis').call(yAxis)
+          .selectAll('.tick')
+          .each(function createTickLines() {
+            d3.select(this).select('line').attr('x2', width);
+          }); // This will select all of the ticks once they're rendered
+
+        this.xScale = d3.time.scale()
+          .range([0, this.graphWidth - 70]);
+
+        this.xScale.domain(d3.extent(this.data, d => d.time));
+
+        const areaFunction = d3.svg.area()
+          .x(d => this.xScale(d.time))
+          .y0(this.graphHeight - this.graphHeightOffset)
+          .y1(d => this.yScale(d.value))
+          .interpolate('linear');
+
+        const lineFunction = d3.svg.line()
+          .x(d => this.xScale(d.time))
+          .y(d => this.yScale(d.value));
+
+        this.line = lineFunction(this.data);
+
+        this.area = areaFunction(this.data);
+      },
+    },
+
+    watch: {
+      updateAspectRatio() {
+        if (this.updateAspectRatio) {
+          this.graphHeight = 450;
+          this.graphWidth = 600;
+          this.measurements = measurements.large;
+          this.draw();
+          eventHub.$emit('toggleAspectRatio');
+        }
+      },
+    },
+
+    mounted() {
+      this.draw();
+    },
+  };
+</script>
+<template>
+  <div 
+    :class="classType">
+    <h5 
+      class="text-center graph-title">
+        {{columnData.title}}
+    </h5>
+    <div
+      class="prometheus-svg-container"
+      :style="paddingBottomRootSvg">
+      <svg 
+        :viewBox="outterViewBox"
+        ref="baseSvg">
+        <g
+          class="x-axis"
+          :transform="axisTransform">
+        </g>
+        <g
+          class="y-axis"
+          transform="translate(70, 20)">
+        </g>
+        <monitoring-legends 
+          :graph-width="graphWidth"
+          :graph-height="graphHeight"
+          :margin="margin"
+          :measurements="measurements"
+          :area-color-rgb="areaColorRgb"
+          :legend-title="legendTitle"
+          :y-axis-label="yAxisLabel"
+          :metric-usage="metricUsage"
+        />
+        <svg 
+          class="graph-data"
+          :viewBox="innerViewBox"
+          ref="graphData">
+            <path
+              class="metric-area"
+              :d="area"
+              :fill="areaColorRgb"
+              transform="translate(-5, 20)">
+            </path>
+            <path
+              class="metric-line"
+              :d="line"
+              :stroke="lineColorRgb"
+              fill="none"
+              stroke-width="2"
+              transform="translate(-5, 20)">
+            </path>
+            <rect 
+              class="prometheus-graph-overlay"
+              :width="(graphWidth - 70)"
+              :height="(graphHeight - 100)"
+              transform="translate(-5, 20)"
+              ref="graphOverlay"
+              @mousemove="handleMouseOverGraph($event)">
+            </rect>
+            <monitoring-deployment
+              :show-deploy-info="showDeployInfo"
+              :deployment-data="reducedDeploymentData"
+              :graph-height="graphHeight"
+              :graph-height-offset="graphHeightOffset"
+            />
+            <monitoring-flag 
+              v-if="showFlag"
+              :current-x-coordinate="currentXCoordinate"
+              :current-y-coordinate="currentYCoordinate"
+              :current-data="currentData"
+              :current-flag-position="currentFlagPosition"
+              :graph-height="graphHeight"
+              :graph-height-offset="graphHeightOffset"
+            />
+        </svg>
+      </svg>
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/monitoring/components/monitoring_deployment.vue b/app/assets/javascripts/monitoring/components/monitoring_deployment.vue
new file mode 100644
index 0000000000000000000000000000000000000000..e6432ba3191ee7de7a4908c9d5e9179e418eec3c
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/monitoring_deployment.vue
@@ -0,0 +1,136 @@
+<script>
+  import {
+      dateFormat,
+      timeFormat,
+    } from '../constants';
+
+  export default {
+    props: {
+      showDeployInfo: {
+        type: Boolean,
+        required: true,
+      },
+      deploymentData: {
+        type: Array,
+        required: true,
+      },
+      graphHeight: {
+        type: Number,
+        required: true,
+      },
+      graphHeightOffset: {
+        type: Number,
+        required: true,
+      },
+    },
+
+    computed: {
+      calculatedHeight() {
+        return this.graphHeight - this.graphHeightOffset;
+      },
+    },
+
+    methods: {
+      refText(d) {
+        return d.tag ? d.ref : d.sha.slice(0, 6);
+      },
+
+      formatTime(deploymentTime) {
+        return timeFormat(deploymentTime);
+      },
+
+      formatDate(deploymentTime) {
+        return dateFormat(deploymentTime);
+      },
+
+      nameDeploymentClass(deployment) {
+        return `deploy-info-${deployment.id}`;
+      },
+
+      transformDeploymentGroup(deployment) {
+        return `translate(${Math.floor(deployment.xPos) + 1}, 20)`;
+      },
+    },
+  };
+</script>
+<template>
+  <g
+    class="deploy-info"
+    v-if="showDeployInfo">
+    <g
+      v-for="(deployment, index) in deploymentData" 
+      :key="index"
+      :class="nameDeploymentClass(deployment)"
+      :transform="transformDeploymentGroup(deployment)">
+      <rect
+        x="0"
+        y="0"
+        :height="calculatedHeight"
+        width="3"
+        fill="url(#shadow-gradient)">
+      </rect>
+      <line
+        class="deployment-line"
+        x1="0"
+        y1="0"
+        x2="0"
+        :y2="calculatedHeight"
+        stroke="#000">
+      </line>
+      <svg
+        v-if="deployment.showDeploymentFlag"
+        class="js-deploy-info-box"
+        x="3"
+        y="0"
+        width="92"
+        height="60">
+        <rect
+          class="rect-text-metric deploy-info-rect rect-metric"
+          x="1"
+          y="1"
+          rx="2"
+          width="90"
+          height="58">
+        </rect>
+        <g 
+          transform="translate(5, 2)">
+          <text
+            class="deploy-info-text text-metric-bold">
+            {{refText(deployment)}}
+          </text>
+        </g>
+        <text
+          class="deploy-info-text"
+          y="18"
+          transform="translate(5, 2)">
+          {{formatDate(deployment.time)}}
+        </text>
+        <text
+          class="deploy-info-text text-metric-bold"
+          y="38"
+          transform="translate(5, 2)">
+          {{formatTime(deployment.time)}}
+        </text>
+      </svg>
+    </g>
+    <svg
+      height="0"
+      width="0">
+      <defs>
+        <linearGradient
+          id="shadow-gradient">
+          <stop
+            offset="0%"
+            stop-color="#000"
+            stop-opacity="0.4">
+          </stop>
+          <stop
+            offset="100%"
+            stop-color="#000"
+            stop-opacity="0">
+          </stop>
+        </linearGradient>
+      </defs>
+    </svg>
+  </g>
+</template>
diff --git a/app/assets/javascripts/monitoring/components/monitoring_flag.vue b/app/assets/javascripts/monitoring/components/monitoring_flag.vue
new file mode 100644
index 0000000000000000000000000000000000000000..180a771415b70f82d438497139894e58faec0186
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/monitoring_flag.vue
@@ -0,0 +1,104 @@
+<script>
+  import {
+      dateFormat,
+      timeFormat,
+    } from '../constants';
+
+  export default {
+    props: {
+      currentXCoordinate: {
+        type: Number,
+        required: true,
+      },
+      currentYCoordinate: {
+        type: Number,
+        required: true,
+      },
+      currentFlagPosition: {
+        type: Number,
+        required: true,
+      },
+      currentData: {
+        type: Object,
+        required: true,
+      },
+      graphHeight: {
+        type: Number,
+        required: true,
+      },
+      graphHeightOffset: {
+        type: Number,
+        required: true,
+      },
+    },
+
+    data() {
+      return {
+        circleColorRgb: '#8fbce8',
+      };
+    },
+
+    computed: {
+      formatTime() {
+        return timeFormat(this.currentData.time);
+      },
+
+      formatDate() {
+        return dateFormat(this.currentData.time);
+      },
+
+      calculatedHeight() {
+        return this.graphHeight - this.graphHeightOffset;
+      },
+    },
+  };
+</script>
+<template>
+  <g class="mouse-over-flag">
+    <line
+      class="selected-metric-line"
+      :x1="currentXCoordinate"
+      :y1="0"
+      :x2="currentXCoordinate"
+      :y2="calculatedHeight"
+      transform="translate(-5, 20)">
+    </line>
+    <circle
+      class="circle-metric"
+      :fill="circleColorRgb"
+      stroke="#000"
+      :cx="currentXCoordinate"
+      :cy="currentYCoordinate"
+      r="5"
+      transform="translate(-5, 20)">
+    </circle>
+    <svg 
+      class="rect-text-metric"
+      :x="currentFlagPosition"
+      y="0">
+      <rect
+        class="rect-metric"
+        x="4"
+        y="1"
+        rx="2"
+        width="90"
+        height="40"
+        transform="translate(-3, 20)">
+      </rect>
+      <text
+        class="text-metric text-metric-bold"
+        x="8"
+        y="35"
+        transform="translate(-5, 20)">
+        {{formatTime}}
+      </text>
+      <text
+        class="text-metric-date"
+        x="8"
+        y="15"
+        transform="translate(-5, 20)">
+        {{formatDate}}
+      </text>
+    </svg>
+  </g>
+</template>
diff --git a/app/assets/javascripts/monitoring/components/monitoring_legends.vue b/app/assets/javascripts/monitoring/components/monitoring_legends.vue
new file mode 100644
index 0000000000000000000000000000000000000000..b30ed3cc889d1eef8d19795a334d96ad902d6871
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/monitoring_legends.vue
@@ -0,0 +1,144 @@
+<script>
+  export default {
+    props: {
+      graphWidth: {
+        type: Number,
+        required: true,
+      },
+      graphHeight: {
+        type: Number,
+        required: true,
+      },
+      margin: {
+        type: Object,
+        required: true,
+      },
+      measurements: {
+        type: Object,
+        required: true,
+      },
+      areaColorRgb: {
+        type: String,
+        required: true,
+      },
+      legendTitle: {
+        type: String,
+        required: true,
+      },
+      yAxisLabel: {
+        type: String,
+        required: true,
+      },
+      metricUsage: {
+        type: String,
+        required: true,
+      },
+    },
+    data() {
+      return {
+        yLabelWidth: 0,
+        yLabelHeight: 0,
+      };
+    },
+    computed: {
+      textTransform() {
+        const yCoordinate = (((this.graphHeight - this.margin.top)
+                          + this.measurements.axisLabelLineOffset) / 2) || 0;
+
+        return `translate(15, ${yCoordinate}) rotate(-90)`;
+      },
+
+      rectTransform() {
+        const yCoordinate = ((this.graphHeight - this.margin.top) / 2)
+                            + (this.yLabelWidth / 2) + 10 || 0;
+
+        return `translate(0, ${yCoordinate}) rotate(-90)`;
+      },
+
+      xPosition() {
+        return (((this.graphWidth + this.measurements.axisLabelLineOffset) / 2)
+               - this.margin.right) || 0;
+      },
+
+      yPosition() {
+        return ((this.graphHeight - this.margin.top) + this.measurements.axisLabelLineOffset) || 0;
+      },
+    },
+    mounted() {
+      this.$nextTick(() => {
+        const bbox = this.$refs.ylabel.getBBox();
+        this.yLabelWidth = bbox.width + 10; // Added some padding
+        this.yLabelHeight = bbox.height + 5;
+      });
+    },
+  };
+</script>
+<template>
+  <g 
+    class="axis-label-container">
+    <line
+      class="label-x-axis-line"
+      stroke="#000000"
+      stroke-width="1"
+      x1="10"
+      :y1="yPosition"
+      :x2="graphWidth + 20"
+      :y2="yPosition">
+    </line>
+    <line
+      class="label-y-axis-line"
+      stroke="#000000"
+      stroke-width="1"
+      x1="10"
+      y1="0"
+      :x2="10"
+      :y2="yPosition">
+    </line>
+    <rect
+      class="rect-axis-text"
+      :transform="rectTransform"
+      :width="yLabelWidth"
+      :height="yLabelHeight">
+    </rect>
+    <text 
+      class="label-axis-text y-label-text"
+      text-anchor="middle"
+      :transform="textTransform"
+      ref="ylabel">
+      {{yAxisLabel}}
+    </text>
+    <rect
+      class="rect-axis-text"
+      :x="xPosition + 50"
+      :y="graphHeight - 80"
+      width="50"
+      height="50">
+    </rect>
+    <text
+      class="label-axis-text"
+      :x="xPosition + 60"
+      :y="yPosition"
+      dy=".35em">
+      Time
+    </text>
+    <rect
+      :fill="areaColorRgb"
+      :width="measurements.legends.width"
+      :height="measurements.legends.height"
+      x="20"
+      :y="graphHeight - measurements.legendOffset">
+    </rect>
+    <text
+      class="text-metric-title"
+      x="50"
+      :y="graphHeight - 40">
+      {{legendTitle}}
+    </text>
+    <text
+      class="text-metric-usage"
+      x="50"
+      :y="graphHeight - 25">
+      {{metricUsage}}
+    </text>
+  </g>
+</template>
diff --git a/app/assets/javascripts/monitoring/components/monitoring_row.vue b/app/assets/javascripts/monitoring/components/monitoring_row.vue
new file mode 100644
index 0000000000000000000000000000000000000000..e5528f1788036106b66502bc935e9d862adcc910
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/monitoring_row.vue
@@ -0,0 +1,41 @@
+<script>
+  import monitoringColumn from './monitoring_column.vue';
+
+  export default {
+    props: {
+      rowData: {
+        type: Array,
+        required: true,
+      },
+      updateAspectRatio: {
+        type: Boolean,
+        required: true,
+      },
+      deploymentData: {
+        type: Array,
+        required: true,
+      },
+    },
+    components: {
+      monitoringColumn,
+    },
+    computed: {
+      bootstrapClass() {
+        return this.rowData.length >= 2 ? 'col-md-6' : 'col-md-12';
+      },
+    },
+  };
+</script>
+<template>
+  <div 
+    class="prometheus-row row">
+    <monitoring-column
+      v-for="(column, index) in rowData" 
+      :column-data="column"
+      :class-type="bootstrapClass"
+      :key="index"
+      :update-aspect-ratio="updateAspectRatio"
+      :deployment-data="deploymentData"
+    />
+  </div>
+</template>
diff --git a/app/assets/javascripts/monitoring/components/monitoring_state.vue b/app/assets/javascripts/monitoring/components/monitoring_state.vue
new file mode 100644
index 0000000000000000000000000000000000000000..598021aa4df790ae070146674a4e6e6fa11d1b3a
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/monitoring_state.vue
@@ -0,0 +1,112 @@
+<script>
+  import gettingStartedSvg from 'empty_states/monitoring/_getting_started.svg';
+  import loadingSvg from 'empty_states/monitoring/_loading.svg';
+  import unableToConnectSvg from 'empty_states/monitoring/_unable_to_connect.svg';
+
+  export default {
+    props: {
+      documentationPath: {
+        type: String,
+        required: true,
+      },
+      settingsPath: {
+        type: String,
+        required: false,
+        default: '',
+      },
+      selectedState: {
+        type: String,
+        required: true,
+      },
+    },
+    data() {
+      return {
+        states: {
+          gettingStarted: {
+            svg: gettingStartedSvg,
+            title: 'Get started with performance monitoring',
+            description: 'Stay updated about the performance and health of your environment by configuring Prometheus to monitor your deployments.',
+            buttonText: 'Configure Prometheus',
+          },
+          loading: {
+            svg: loadingSvg,
+            title: 'Waiting for performance data',
+            description: 'Creating graphs uses the data from the Prometheus server. If this takes a long time, ensure that data is available.',
+            buttonText: 'View documentation',
+          },
+          unableToConnect: {
+            svg: unableToConnectSvg,
+            title: 'Unable to connect to Prometheus server',
+            description: 'Ensure connectivity is available from the GitLab server to the ',
+            buttonText: 'View documentation',
+          },
+        },
+      };
+    },
+    computed: {
+      currentState() {
+        return this.states[this.selectedState];
+      },
+
+      buttonPath() {
+        if (this.selectedState === 'gettingStarted') {
+          return this.settingsPath;
+        }
+        return this.documentationPath;
+      },
+
+      showButtonDescription() {
+        if (this.selectedState === 'unableToConnect') return true;
+        return false;
+      },
+    },
+  };
+</script>
+<template>
+  <div 
+    class="prometheus-state">
+    <div 
+      class="row">
+      <div 
+        class="col-md-4 col-md-offset-4 state-svg" 
+        v-html="currentState.svg">
+      </div>
+    </div>
+    <div 
+      class="row">
+      <div 
+        class="col-md-6 col-md-offset-3">
+        <h4 
+          class="text-center state-title">
+          {{currentState.title}}
+        </h4>
+      </div>
+    </div>
+    <div 
+      class="row">
+      <div 
+        class="col-md-6 col-md-offset-3">
+        <div 
+          class="description-text text-center state-description">
+            {{currentState.description}}
+            <a 
+              :href="settingsPath"
+              v-if="showButtonDescription">
+              Prometheus server
+            </a>
+        </div>
+      </div>
+    </div>
+    <div 
+      class="row state-button-section">
+      <div 
+        class="col-md-4 col-md-offset-4 text-center state-button">
+        <a 
+          class="btn btn-success" 
+          :href="buttonPath">
+            {{currentState.buttonText}}
+        </a>
+      </div>
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/monitoring/deployments.js b/app/assets/javascripts/monitoring/deployments.js
deleted file mode 100644
index fc92ab61b31d83e0c3a680ff3ef635f194cdf33e..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/monitoring/deployments.js
+++ /dev/null
@@ -1,211 +0,0 @@
-/* global Flash */
-import d3 from 'd3';
-import {
-  dateFormat,
-  timeFormat,
-} from './constants';
-
-export default class Deployments {
-  constructor(width, height) {
-    this.width = width;
-    this.height = height;
-
-    this.endpoint = document.getElementById('js-metrics').dataset.deploymentEndpoint;
-
-    this.createGradientDef();
-  }
-
-  init(chartData) {
-    this.chartData = chartData;
-
-    this.x = d3.time.scale().range([0, this.width]);
-    this.x.domain(d3.extent(this.chartData, d => d.time));
-
-    this.charts = d3.selectAll('.prometheus-graph');
-
-    this.getData();
-  }
-
-  getData() {
-    $.ajax({
-      url: this.endpoint,
-      dataType: 'JSON',
-    })
-    .fail(() => new Flash('Error getting deployment information.'))
-    .done((data) => {
-      this.data = data.deployments.reduce((deploymentDataArray, deployment) => {
-        const time = new Date(deployment.created_at);
-        const xPos = Math.floor(this.x(time));
-
-        time.setSeconds(this.chartData[0].time.getSeconds());
-
-        if (xPos >= 0) {
-          deploymentDataArray.push({
-            id: deployment.id,
-            time,
-            sha: deployment.sha,
-            tag: deployment.tag,
-            ref: deployment.ref.name,
-            xPos,
-          });
-        }
-
-        return deploymentDataArray;
-      }, []);
-
-      this.plotData();
-    });
-  }
-
-  plotData() {
-    this.charts.each((d, i) => {
-      const svg = d3.select(this.charts[0][i]);
-      const chart = svg.select('.graph-container');
-      const key = svg.node().getAttribute('graph-type');
-
-      this.createLine(chart, key);
-      this.createDeployInfoBox(chart, key);
-    });
-  }
-
-  createGradientDef() {
-    const defs = d3.select('body')
-      .append('svg')
-      .attr({
-        height: 0,
-        width: 0,
-      })
-      .append('defs');
-
-    defs.append('linearGradient')
-      .attr({
-        id: 'shadow-gradient',
-      })
-      .append('stop')
-      .attr({
-        offset: '0%',
-        'stop-color': '#000',
-        'stop-opacity': 0.4,
-      })
-      .select(this.selectParentNode)
-      .append('stop')
-      .attr({
-        offset: '100%',
-        'stop-color': '#000',
-        'stop-opacity': 0,
-      });
-  }
-
-  createLine(chart, key) {
-    chart.append('g')
-      .attr({
-        class: 'deploy-info',
-      })
-      .selectAll('.deploy-info')
-      .data(this.data)
-      .enter()
-      .append('g')
-      .attr({
-        class: d => `deploy-info-${d.id}-${key}`,
-        transform: d => `translate(${Math.floor(d.xPos) + 1}, 0)`,
-      })
-      .append('rect')
-      .attr({
-        x: 1,
-        y: 0,
-        height: this.height + 1,
-        width: 3,
-        fill: 'url(#shadow-gradient)',
-      })
-      .select(this.selectParentNode)
-      .append('line')
-      .attr({
-        class: 'deployment-line',
-        x1: 0,
-        x2: 0,
-        y1: 0,
-        y2: this.height + 1,
-      });
-  }
-
-  createDeployInfoBox(chart, key) {
-    chart.selectAll('.deploy-info')
-      .selectAll('.js-deploy-info-box')
-      .data(this.data)
-      .enter()
-      .select(d => document.querySelector(`.deploy-info-${d.id}-${key}`))
-      .append('svg')
-      .attr({
-        class: 'js-deploy-info-box hidden',
-        x: 3,
-        y: 0,
-        width: 92,
-        height: 60,
-      })
-      .append('rect')
-      .attr({
-        class: 'rect-text-metric deploy-info-rect rect-metric',
-        x: 1,
-        y: 1,
-        rx: 2,
-        width: 90,
-        height: 58,
-      })
-      .select(this.selectParentNode)
-      .append('g')
-      .attr({
-        transform: 'translate(5, 2)',
-      })
-      .append('text')
-      .attr({
-        class: 'deploy-info-text text-metric-bold',
-      })
-      .text(Deployments.refText)
-      .select(this.selectParentNode)
-      .append('text')
-      .attr({
-        class: 'deploy-info-text',
-        y: 18,
-      })
-      .text(d => dateFormat(d.time))
-      .select(this.selectParentNode)
-      .append('text')
-      .attr({
-        class: 'deploy-info-text text-metric-bold',
-        y: 38,
-      })
-      .text(d => timeFormat(d.time));
-  }
-
-  static toggleDeployTextbox(deploy, key, showInfoBox) {
-    d3.selectAll(`.deploy-info-${deploy.id}-${key} .js-deploy-info-box`)
-      .classed('hidden', !showInfoBox);
-  }
-
-  mouseOverDeployInfo(mouseXPos, key) {
-    if (!this.data) return false;
-
-    let dataFound = false;
-
-    this.data.forEach((d) => {
-      if (d.xPos >= mouseXPos - 10 && d.xPos <= mouseXPos + 10 && !dataFound) {
-        dataFound = d.xPos + 1;
-
-        Deployments.toggleDeployTextbox(d, key, true);
-      } else {
-        Deployments.toggleDeployTextbox(d, key, false);
-      }
-    });
-
-    return dataFound;
-  }
-
-  /* `this` is bound to the D3 node */
-  selectParentNode() {
-    return this.parentNode;
-  }
-
-  static refText(d) {
-    return d.tag ? d.ref : d.sha.slice(0, 6);
-  }
-}
diff --git a/app/assets/javascripts/monitoring/event_hub.js b/app/assets/javascripts/monitoring/event_hub.js
new file mode 100644
index 0000000000000000000000000000000000000000..0948c2e53524a736a55c060600868ce89ee7687a
--- /dev/null
+++ b/app/assets/javascripts/monitoring/event_hub.js
@@ -0,0 +1,3 @@
+import Vue from 'vue';
+
+export default new Vue();
diff --git a/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js b/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js
new file mode 100644
index 0000000000000000000000000000000000000000..8e62fa63f1363d1dbff4c8edf74746828856d86a
--- /dev/null
+++ b/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js
@@ -0,0 +1,46 @@
+const mixins = {
+  methods: {
+    mouseOverDeployInfo(mouseXPos) {
+      if (!this.reducedDeploymentData) return false;
+
+      let dataFound = false;
+      this.reducedDeploymentData = this.reducedDeploymentData.map((d) => {
+        const deployment = d;
+        if (d.xPos >= mouseXPos - 10 && d.xPos <= mouseXPos + 10 && !dataFound) {
+          dataFound = d.xPos + 1;
+
+          deployment.showDeploymentFlag = true;
+        } else {
+          deployment.showDeploymentFlag = false;
+        }
+        return deployment;
+      });
+
+      return dataFound;
+    },
+    formatDeployments() {
+      this.reducedDeploymentData = this.deploymentData.reduce((deploymentDataArray, deployment) => {
+        const time = new Date(deployment.created_at);
+        const xPos = Math.floor(this.xScale(time));
+
+        time.setSeconds(this.data[0].time.getSeconds());
+
+        if (xPos >= 0) {
+          deploymentDataArray.push({
+            id: deployment.id,
+            time,
+            sha: deployment.sha,
+            tag: deployment.tag,
+            ref: deployment.ref.name,
+            xPos,
+            showDeploymentFlag: false,
+          });
+        }
+
+        return deploymentDataArray;
+      }, []);
+    },
+  },
+};
+
+export default mixins;
diff --git a/app/assets/javascripts/monitoring/monitoring_bundle.js b/app/assets/javascripts/monitoring/monitoring_bundle.js
index b3ce931041767f1dcacb761e36a13bc0afbe6196..5d5cb56af72c397560ed347ff48ec75602e8c68a 100644
--- a/app/assets/javascripts/monitoring/monitoring_bundle.js
+++ b/app/assets/javascripts/monitoring/monitoring_bundle.js
@@ -1,6 +1,10 @@
-import PrometheusGraph from './prometheus_graph';
+import Vue from 'vue';
+import Monitoring from './components/monitoring.vue';
 
-document.addEventListener('DOMContentLoaded', function onLoad() {
-  document.removeEventListener('DOMContentLoaded', onLoad, false);
-  return new PrometheusGraph();
-}, false);
+document.addEventListener('DOMContentLoaded', () => new Vue({
+  el: '#prometheus-graphs',
+  components: {
+    'monitoring-dashboard': Monitoring,
+  },
+  render: createElement => createElement('monitoring-dashboard'),
+}));
diff --git a/app/assets/javascripts/monitoring/prometheus_graph.js b/app/assets/javascripts/monitoring/prometheus_graph.js
deleted file mode 100644
index 6af887691292e1de6e3c013a715417e78c8d101c..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/monitoring/prometheus_graph.js
+++ /dev/null
@@ -1,433 +0,0 @@
-/* eslint-disable no-new */
-/* global Flash */
-
-import d3 from 'd3';
-import statusCodes from '~/lib/utils/http_status';
-import Deployments from './deployments';
-import '../lib/utils/common_utils';
-import { formatRelevantDigits } from '../lib/utils/number_utils';
-import '../flash';
-import {
-  dateFormat,
-  timeFormat,
-} from './constants';
-
-const prometheusContainer = '.prometheus-container';
-const prometheusParentGraphContainer = '.prometheus-graphs';
-const prometheusGraphsContainer = '.prometheus-graph';
-const prometheusStatesContainer = '.prometheus-state';
-const metricsEndpoint = 'metrics.json';
-const bisectDate = d3.bisector(d => d.time).left;
-const extraAddedWidthParent = 100;
-
-class PrometheusGraph {
-  constructor() {
-    const $prometheusContainer = $(prometheusContainer);
-    const hasMetrics = $prometheusContainer.data('has-metrics');
-    this.docLink = $prometheusContainer.data('doc-link');
-    this.integrationLink = $prometheusContainer.data('prometheus-integration');
-    this.state = '';
-
-    $(document).ajaxError(() => {});
-
-    if (hasMetrics) {
-      this.margin = { top: 80, right: 180, bottom: 80, left: 100 };
-      this.marginLabelContainer = { top: 40, right: 0, bottom: 40, left: 0 };
-      const parentContainerWidth = $(prometheusGraphsContainer).parent().width() +
-      extraAddedWidthParent;
-      this.originalWidth = parentContainerWidth;
-      this.originalHeight = 330;
-      this.width = parentContainerWidth - this.margin.left - this.margin.right;
-      this.height = this.originalHeight - this.margin.top - this.margin.bottom;
-      this.backOffRequestCounter = 0;
-      this.deployments = new Deployments(this.width, this.height);
-      this.configureGraph();
-      this.init();
-    } else {
-      const prevState = this.state;
-      this.state = '.js-getting-started';
-      this.updateState(prevState);
-    }
-  }
-
-  createGraph() {
-    Object.keys(this.graphSpecificProperties).forEach((key) => {
-      const value = this.graphSpecificProperties[key];
-      if (value.data.length > 0) {
-        this.plotValues(key);
-      }
-    });
-  }
-
-  init() {
-    return this.getData().then((metricsResponse) => {
-      let enoughData = true;
-      if (typeof metricsResponse === 'undefined') {
-        enoughData = false;
-      } else {
-        Object.keys(metricsResponse.metrics).forEach((key) => {
-          if (key === 'cpu_values' || key === 'memory_values') {
-            const currentData = (metricsResponse.metrics[key])[0];
-            if (currentData.values.length <= 2) {
-              enoughData = false;
-            }
-          }
-        });
-      }
-      if (enoughData) {
-        $(prometheusStatesContainer).hide();
-        $(prometheusParentGraphContainer).show();
-        this.transformData(metricsResponse);
-        this.createGraph();
-
-        const firstMetricData = this.graphSpecificProperties[
-          Object.keys(this.graphSpecificProperties)[0]
-        ].data;
-
-        this.deployments.init(firstMetricData);
-      }
-    });
-  }
-
-  plotValues(key) {
-    const graphSpecifics = this.graphSpecificProperties[key];
-
-    const x = d3.time.scale()
-        .range([0, this.width]);
-
-    const y = d3.scale.linear()
-        .range([this.height, 0]);
-
-    graphSpecifics.xScale = x;
-    graphSpecifics.yScale = y;
-
-    const prometheusGraphContainer = `${prometheusGraphsContainer}[graph-type=${key}]`;
-
-    const chart = d3.select(prometheusGraphContainer)
-      .attr('width', this.width + this.margin.left + this.margin.right)
-      .attr('height', this.height + this.margin.bottom + this.margin.top)
-      .append('g')
-      .attr('class', 'graph-container')
-        .attr('transform', `translate(${this.margin.left},${this.margin.top})`);
-
-    const axisLabelContainer = d3.select(prometheusGraphContainer)
-      .attr('width', this.originalWidth)
-      .attr('height', this.originalHeight)
-      .append('g')
-        .attr('transform', `translate(${this.marginLabelContainer.left},${this.marginLabelContainer.top})`);
-
-    x.domain(d3.extent(graphSpecifics.data, d => d.time));
-    y.domain([0, d3.max(graphSpecifics.data.map(metricValue => metricValue.value))]);
-
-    const xAxis = d3.svg.axis()
-      .scale(x)
-      .ticks(this.commonGraphProperties.axis_no_ticks)
-      .orient('bottom');
-
-    const yAxis = d3.svg.axis()
-      .scale(y)
-      .ticks(this.commonGraphProperties.axis_no_ticks)
-      .tickSize(-this.width)
-      .outerTickSize(0)
-      .orient('left');
-
-    this.createAxisLabelContainers(axisLabelContainer, key);
-
-    chart.append('g')
-      .attr('class', 'x-axis')
-      .attr('transform', `translate(0,${this.height})`)
-      .call(xAxis);
-
-    chart.append('g')
-      .attr('class', 'y-axis')
-      .call(yAxis);
-
-    const area = d3.svg.area()
-      .x(d => x(d.time))
-      .y0(this.height)
-      .y1(d => y(d.value))
-      .interpolate('linear');
-
-    const line = d3.svg.line()
-    .x(d => x(d.time))
-    .y(d => y(d.value));
-
-    chart.append('path')
-      .datum(graphSpecifics.data)
-      .attr('d', area)
-      .attr('class', 'metric-area')
-      .attr('fill', graphSpecifics.area_fill_color);
-
-    chart.append('path')
-      .datum(graphSpecifics.data)
-      .attr('class', 'metric-line')
-      .attr('stroke', graphSpecifics.line_color)
-      .attr('fill', 'none')
-      .attr('stroke-width', this.commonGraphProperties.area_stroke_width)
-      .attr('d', line);
-
-    // Overlay area for the mouseover events
-    chart.append('rect')
-      .attr('class', 'prometheus-graph-overlay')
-      .attr('width', this.width)
-      .attr('height', this.height)
-      .on('mousemove', this.handleMouseOverGraph.bind(this, prometheusGraphContainer));
-  }
-
-  // The legends from the metric
-  createAxisLabelContainers(axisLabelContainer, key) {
-    const graphSpecifics = this.graphSpecificProperties[key];
-
-    axisLabelContainer.append('line')
-      .attr('class', 'label-x-axis-line')
-      .attr('stroke', '#000000')
-      .attr('stroke-width', '1')
-      .attr({
-        x1: 10,
-        y1: this.originalHeight - this.margin.top,
-        x2: (this.originalWidth - this.margin.right) + 10,
-        y2: this.originalHeight - this.margin.top,
-      });
-
-    axisLabelContainer.append('line')
-      .attr('class', 'label-y-axis-line')
-      .attr('stroke', '#000000')
-      .attr('stroke-width', '1')
-      .attr({
-        x1: 10,
-        y1: 0,
-        x2: 10,
-        y2: this.originalHeight - this.margin.top,
-      });
-
-    axisLabelContainer.append('rect')
-      .attr('class', 'rect-axis-text')
-      .attr('x', 0)
-      .attr('y', 50)
-      .attr('width', 30)
-      .attr('height', 150);
-
-    axisLabelContainer.append('text')
-      .attr('class', 'label-axis-text')
-      .attr('text-anchor', 'middle')
-      .attr('transform', `translate(15, ${(this.originalHeight - this.margin.top) / 2}) rotate(-90)`)
-      .text(graphSpecifics.graph_legend_title);
-
-    axisLabelContainer.append('rect')
-      .attr('class', 'rect-axis-text')
-      .attr('x', (this.originalWidth / 2) - this.margin.right)
-      .attr('y', this.originalHeight - 100)
-      .attr('width', 30)
-      .attr('height', 80);
-
-    axisLabelContainer.append('text')
-      .attr('class', 'label-axis-text')
-      .attr('x', (this.originalWidth / 2) - this.margin.right)
-      .attr('y', this.originalHeight - this.margin.top)
-      .attr('dy', '.35em')
-      .text('Time');
-
-    // Legends
-
-    // Metric Usage
-    axisLabelContainer.append('rect')
-      .attr('x', this.originalWidth - 170)
-      .attr('y', (this.originalHeight / 2) - 60)
-      .style('fill', graphSpecifics.area_fill_color)
-      .attr('width', 20)
-      .attr('height', 35);
-
-    axisLabelContainer.append('text')
-      .attr('class', 'text-metric-title')
-      .attr('x', this.originalWidth - 140)
-      .attr('y', (this.originalHeight / 2) - 50)
-      .text('Average');
-
-    axisLabelContainer.append('text')
-      .attr('class', 'text-metric-usage')
-      .attr('x', this.originalWidth - 140)
-      .attr('y', (this.originalHeight / 2) - 25);
-  }
-
-  handleMouseOverGraph(prometheusGraphContainer) {
-    const rectOverlay = document.querySelector(`${prometheusGraphContainer} .prometheus-graph-overlay`);
-    const currentXCoordinate = d3.mouse(rectOverlay)[0];
-
-    Object.keys(this.graphSpecificProperties).forEach((key) => {
-      const currentGraphProps = this.graphSpecificProperties[key];
-      const timeValueOverlay = currentGraphProps.xScale.invert(currentXCoordinate);
-      const overlayIndex = bisectDate(currentGraphProps.data, timeValueOverlay, 1);
-      const d0 = currentGraphProps.data[overlayIndex - 1];
-      const d1 = currentGraphProps.data[overlayIndex];
-      const evalTime = timeValueOverlay - d0.time > d1.time - timeValueOverlay;
-      const currentData = evalTime ? d1 : d0;
-      const currentTimeCoordinate = Math.floor(currentGraphProps.xScale(currentData.time));
-      const currentDeployXPos = this.deployments.mouseOverDeployInfo(currentXCoordinate, key);
-      const currentPrometheusGraphContainer = `${prometheusGraphsContainer}[graph-type=${key}]`;
-      const maxValueFromData = d3.max(currentGraphProps.data.map(metricValue => metricValue.value));
-      const maxMetricValue = currentGraphProps.yScale(maxValueFromData);
-
-      // Clear up all the pieces of the flag
-      d3.selectAll(`${currentPrometheusGraphContainer} .selected-metric-line`).remove();
-      d3.selectAll(`${currentPrometheusGraphContainer} .circle-metric`).remove();
-      d3.selectAll(`${currentPrometheusGraphContainer} .rect-text-metric:not(.deploy-info-rect)`).remove();
-
-      const currentChart = d3.select(currentPrometheusGraphContainer).select('g');
-      currentChart.append('line')
-      .attr({
-        class: `${currentDeployXPos ? 'hidden' : ''} selected-metric-line`,
-        x1: currentTimeCoordinate,
-        y1: currentGraphProps.yScale(0),
-        x2: currentTimeCoordinate,
-        y2: maxMetricValue,
-      });
-
-      currentChart.append('circle')
-        .attr('class', 'circle-metric')
-        .attr('fill', currentGraphProps.line_color)
-        .attr('cx', currentDeployXPos || currentTimeCoordinate)
-        .attr('cy', currentGraphProps.yScale(currentData.value))
-        .attr('r', this.commonGraphProperties.circle_radius_metric);
-
-      if (currentDeployXPos) return;
-
-      // The little box with text
-      const rectTextMetric = currentChart.append('svg')
-        .attr({
-          class: 'rect-text-metric',
-          x: currentTimeCoordinate,
-          y: 0,
-        });
-
-      rectTextMetric.append('rect')
-        .attr({
-          class: 'rect-metric',
-          x: 4,
-          y: 1,
-          rx: 2,
-          width: this.commonGraphProperties.rect_text_width,
-          height: this.commonGraphProperties.rect_text_height,
-        });
-
-      rectTextMetric.append('text')
-        .attr({
-          class: 'text-metric text-metric-bold',
-          x: 8,
-          y: 35,
-        })
-        .text(timeFormat(currentData.time));
-
-      rectTextMetric.append('text')
-        .attr({
-          class: 'text-metric-date',
-          x: 8,
-          y: 15,
-        })
-        .text(dateFormat(currentData.time));
-
-      let currentMetricValue = formatRelevantDigits(currentData.value);
-      if (key === 'cpu_values') {
-        currentMetricValue = `${currentMetricValue}%`;
-      } else {
-        currentMetricValue = `${currentMetricValue} MB`;
-      }
-
-      d3.select(`${currentPrometheusGraphContainer} .text-metric-usage`)
-        .text(currentMetricValue);
-    });
-  }
-
-  configureGraph() {
-    this.graphSpecificProperties = {
-      cpu_values: {
-        area_fill_color: '#edf3fc',
-        line_color: '#5b99f7',
-        graph_legend_title: 'CPU Usage (Cores)',
-        data: [],
-        xScale: {},
-        yScale: {},
-      },
-      memory_values: {
-        area_fill_color: '#fca326',
-        line_color: '#fc6d26',
-        graph_legend_title: 'Memory Usage (MB)',
-        data: [],
-        xScale: {},
-        yScale: {},
-      },
-    };
-
-    this.commonGraphProperties = {
-      area_stroke_width: 2,
-      median_total_characters: 8,
-      circle_radius_metric: 5,
-      rect_text_width: 90,
-      rect_text_height: 40,
-      axis_no_ticks: 3,
-    };
-  }
-
-  getData() {
-    const maxNumberOfRequests = 3;
-    this.state = '.js-loading';
-    this.updateState();
-    return gl.utils.backOff((next, stop) => {
-      $.ajax({
-        url: metricsEndpoint,
-        dataType: 'json',
-      })
-      .done((data, statusText, resp) => {
-        if (resp.status === statusCodes.NO_CONTENT) {
-          this.backOffRequestCounter = this.backOffRequestCounter += 1;
-          if (this.backOffRequestCounter < maxNumberOfRequests) {
-            next();
-          } else if (this.backOffRequestCounter >= maxNumberOfRequests) {
-            stop(new Error('loading'));
-          }
-        } else if (!data.success) {
-          stop(new Error('loading'));
-        } else {
-          stop({
-            status: resp.status,
-            metrics: data,
-          });
-        }
-      }).fail(stop);
-    })
-    .then((resp) => {
-      if (resp.status === statusCodes.NO_CONTENT) {
-        return {};
-      }
-      return resp.metrics;
-    })
-    .catch(() => {
-      const prevState = this.state;
-      this.state = '.js-unable-to-connect';
-      this.updateState(prevState);
-    });
-  }
-
-  transformData(metricsResponse) {
-    Object.keys(metricsResponse.metrics).forEach((key) => {
-      if (key === 'cpu_values' || key === 'memory_values') {
-        const metricValues = (metricsResponse.metrics[key])[0];
-        this.graphSpecificProperties[key].data = metricValues.values.map(metric => ({
-          time: new Date(metric[0] * 1000),
-          value: metric[1],
-        }));
-      }
-    });
-  }
-
-  updateState(prevState) {
-    const $statesContainer = $(prometheusStatesContainer);
-    $(prometheusParentGraphContainer).hide();
-    if (prevState) {
-      $(`${prevState}`, $statesContainer).addClass('hidden');
-    }
-    $(`${this.state}`, $statesContainer).removeClass('hidden');
-    $(prometheusStatesContainer).show();
-  }
-}
-
-export default PrometheusGraph;
diff --git a/app/assets/javascripts/monitoring/services/monitoring_service.js b/app/assets/javascripts/monitoring/services/monitoring_service.js
new file mode 100644
index 0000000000000000000000000000000000000000..1e9ae93485365bfb6cde1f2f7146e3732ea42a34
--- /dev/null
+++ b/app/assets/javascripts/monitoring/services/monitoring_service.js
@@ -0,0 +1,19 @@
+import Vue from 'vue';
+import VueResource from 'vue-resource';
+
+Vue.use(VueResource);
+
+export default class MonitoringService {
+  constructor(endpoint) {
+    this.graphs = Vue.resource(endpoint);
+  }
+
+  get() {
+    return this.graphs.get();
+  }
+
+  // eslint-disable-next-line class-methods-use-this
+  getDeploymentData(endpoint) {
+    return Vue.http.get(endpoint);
+  }
+}
diff --git a/app/assets/javascripts/monitoring/stores/monitoring_store.js b/app/assets/javascripts/monitoring/stores/monitoring_store.js
new file mode 100644
index 0000000000000000000000000000000000000000..737c964f12ed0ea4cb5688d030e55c9632372f06
--- /dev/null
+++ b/app/assets/javascripts/monitoring/stores/monitoring_store.js
@@ -0,0 +1,61 @@
+import _ from 'underscore';
+
+class MonitoringStore {
+  constructor() {
+    this.groups = [];
+    this.deploymentData = [];
+  }
+
+  // eslint-disable-next-line class-methods-use-this
+  createArrayRows(metrics = []) {
+    const currentMetrics = metrics;
+    const availableMetrics = [];
+    let metricsRow = [];
+    let index = 1;
+    Object.keys(currentMetrics).forEach((key) => {
+      const metricValues = currentMetrics[key].queries[0].result[0].values;
+      if (metricValues != null) {
+        const literalMetrics = metricValues.map(metric => ({
+          time: new Date(metric[0] * 1000),
+          value: metric[1],
+        }));
+        currentMetrics[key].queries[0].result[0].values = literalMetrics;
+        metricsRow.push(currentMetrics[key]);
+        if (index % 2 === 0) {
+          availableMetrics.push(metricsRow);
+          metricsRow = [];
+        }
+        index = index += 1;
+      }
+    });
+    if (metricsRow.length > 0) {
+      availableMetrics.push(metricsRow);
+    }
+    return availableMetrics;
+  }
+
+  storeMetrics(groups = []) {
+    this.groups = groups.map((group) => {
+      const currentGroup = group;
+      currentGroup.metrics = _.chain(group.metrics).sortBy('weight').sortBy('title').value();
+      currentGroup.metrics = this.createArrayRows(currentGroup.metrics);
+      return currentGroup;
+    });
+  }
+
+  storeDeploymentData(deploymentData = []) {
+    this.deploymentData = deploymentData;
+  }
+
+  getMetricsCount() {
+    let metricsCount = 0;
+    this.groups.forEach((group) => {
+      group.metrics.forEach((metric) => {
+        metricsCount = metricsCount += metric.length;
+      });
+    });
+    return metricsCount;
+  }
+}
+
+export default MonitoringStore;
diff --git a/app/assets/javascripts/monitoring/utils/measurements.js b/app/assets/javascripts/monitoring/utils/measurements.js
new file mode 100644
index 0000000000000000000000000000000000000000..a60d2522f49ac730d923883024a31e8a5948819f
--- /dev/null
+++ b/app/assets/javascripts/monitoring/utils/measurements.js
@@ -0,0 +1,39 @@
+export default {
+  small: { // Covers both xs and sm screen sizes
+    margin: {
+      top: 40,
+      right: 40,
+      bottom: 50,
+      left: 40,
+    },
+    legends: {
+      width: 15,
+      height: 30,
+    },
+    backgroundLegend: {
+      width: 30,
+      height: 50,
+    },
+    axisLabelLineOffset: -20,
+    legendOffset: 52,
+  },
+  large: { // This covers both md and lg screen sizes
+    margin: {
+      top: 80,
+      right: 80,
+      bottom: 100,
+      left: 80,
+    },
+    legends: {
+      width: 20,
+      height: 35,
+    },
+    backgroundLegend: {
+      width: 30,
+      height: 150,
+    },
+    axisLabelLineOffset: 20,
+    legendOffset: 55,
+  },
+  ticks: 3,
+};
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index d56cf959486b93c54191be31a9c4a6fb280b460e..555b8c8a65cbfd2099a910b30ea2d84f1ff7b272 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -4,7 +4,7 @@ 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 */
+newline-per-chained-call, no-useless-escape, class-methods-use-this */
 /* global Flash */
 /* global Autosave */
 /* global ResolveService */
@@ -18,6 +18,7 @@ import 'vendor/jquery.caret'; // required by jquery.atwho
 import 'vendor/jquery.atwho';
 import AjaxCache from '~/lib/utils/ajax_cache';
 import CommentTypeToggle from './comment_type_toggle';
+import loadAwardsHandler from './awards_handler';
 import './autosave';
 import './dropzone_input';
 import './task_list';
@@ -25,1505 +26,1501 @@ import './task_list';
 window.autosize = autosize;
 window.Dropzone = Dropzone;
 
-const normalizeNewlines = function(str) {
+function normalizeNewlines(str) {
   return str.replace(/\r\n/g, '\n');
-};
-
-(function() {
-  this.Notes = (function() {
-    const MAX_VISIBLE_COMMIT_LIST_COUNT = 3;
-    const REGEX_SLASH_COMMANDS = /^\/\w+.*$/gm;
-
-    Notes.interval = null;
-
-    function Notes(notes_url, note_ids, last_fetched_at, view, enableGFM = true) {
-      this.updateTargetButtons = this.updateTargetButtons.bind(this);
-      this.updateComment = this.updateComment.bind(this);
-      this.visibilityChange = this.visibilityChange.bind(this);
-      this.cancelDiscussionForm = this.cancelDiscussionForm.bind(this);
-      this.onAddDiffNote = this.onAddDiffNote.bind(this);
-      this.setupDiscussionNoteForm = this.setupDiscussionNoteForm.bind(this);
-      this.onReplyToDiscussionNote = this.onReplyToDiscussionNote.bind(this);
-      this.removeNote = this.removeNote.bind(this);
-      this.cancelEdit = this.cancelEdit.bind(this);
-      this.updateNote = this.updateNote.bind(this);
-      this.addDiscussionNote = this.addDiscussionNote.bind(this);
-      this.addNoteError = this.addNoteError.bind(this);
-      this.addNote = this.addNote.bind(this);
-      this.resetMainTargetForm = this.resetMainTargetForm.bind(this);
-      this.refresh = this.refresh.bind(this);
-      this.keydownNoteText = this.keydownNoteText.bind(this);
-      this.toggleCommitList = this.toggleCommitList.bind(this);
-      this.postComment = this.postComment.bind(this);
-      this.clearFlashWrapper = this.clearFlash.bind(this);
-      this.onHashChange = this.onHashChange.bind(this);
-
-      this.notes_url = notes_url;
-      this.note_ids = note_ids;
-      this.enableGFM = enableGFM;
-      // Used to keep track of updated notes while people are editing things
-      this.updatedNotesTrackingMap = {};
-      this.last_fetched_at = last_fetched_at;
-      this.noteable_url = document.URL;
-      this.notesCountBadge || (this.notesCountBadge = $('.issuable-details').find('.notes-tab .badge'));
-      this.basePollingInterval = 15000;
-      this.maxPollingSteps = 4;
-
-      this.cleanBinding();
-      this.addBinding();
-      this.setPollingInterval();
-      this.setupMainTargetNoteForm();
-      this.taskList = new gl.TaskList({
-        dataType: 'note',
-        fieldName: 'note',
-        selector: '.notes'
-      });
-      this.collapseLongCommitList();
-      this.setViewType(view);
-
-      // We are in the Merge Requests page so we need another edit form for Changes tab
-      if (gl.utils.getPagePath(1) === 'merge_requests') {
-        $('.note-edit-form').clone()
-          .addClass('mr-note-edit-form').insertAfter('.note-edit-form');
-      }
+}
+
+const MAX_VISIBLE_COMMIT_LIST_COUNT = 3;
+const REGEX_QUICK_ACTIONS = /^\/\w+.*$/gm;
+
+export default class Notes {
+  constructor(notes_url, note_ids, last_fetched_at, view, enableGFM = true) {
+    this.updateTargetButtons = this.updateTargetButtons.bind(this);
+    this.updateComment = this.updateComment.bind(this);
+    this.visibilityChange = this.visibilityChange.bind(this);
+    this.cancelDiscussionForm = this.cancelDiscussionForm.bind(this);
+    this.onAddDiffNote = this.onAddDiffNote.bind(this);
+    this.setupDiscussionNoteForm = this.setupDiscussionNoteForm.bind(this);
+    this.onReplyToDiscussionNote = this.onReplyToDiscussionNote.bind(this);
+    this.removeNote = this.removeNote.bind(this);
+    this.cancelEdit = this.cancelEdit.bind(this);
+    this.updateNote = this.updateNote.bind(this);
+    this.addDiscussionNote = this.addDiscussionNote.bind(this);
+    this.addNoteError = this.addNoteError.bind(this);
+    this.addNote = this.addNote.bind(this);
+    this.resetMainTargetForm = this.resetMainTargetForm.bind(this);
+    this.refresh = this.refresh.bind(this);
+    this.keydownNoteText = this.keydownNoteText.bind(this);
+    this.toggleCommitList = this.toggleCommitList.bind(this);
+    this.postComment = this.postComment.bind(this);
+    this.clearFlashWrapper = this.clearFlash.bind(this);
+    this.onHashChange = this.onHashChange.bind(this);
+
+    this.notes_url = notes_url;
+    this.note_ids = note_ids;
+    this.enableGFM = enableGFM;
+    // Used to keep track of updated notes while people are editing things
+    this.updatedNotesTrackingMap = {};
+    this.last_fetched_at = last_fetched_at;
+    this.noteable_url = document.URL;
+    this.notesCountBadge || (this.notesCountBadge = $('.issuable-details').find('.notes-tab .badge'));
+    this.basePollingInterval = 15000;
+    this.maxPollingSteps = 4;
+
+    this.cleanBinding();
+    this.addBinding();
+    this.setPollingInterval();
+    this.setupMainTargetNoteForm();
+    this.taskList = new gl.TaskList({
+      dataType: 'note',
+      fieldName: 'note',
+      selector: '.notes'
+    });
+    this.collapseLongCommitList();
+    this.setViewType(view);
+
+    // We are in the Merge Requests page so we need another edit form for Changes tab
+    if (gl.utils.getPagePath(1) === 'merge_requests') {
+      $('.note-edit-form').clone()
+        .addClass('mr-note-edit-form').insertAfter('.note-edit-form');
+    }
+  }
+
+  setViewType(view) {
+    this.view = Cookies.get('diff_view') || view;
+  }
+
+  addBinding() {
+    // Edit note link
+    $(document).on('click', '.js-note-edit', this.showEditForm.bind(this));
+    $(document).on('click', '.note-edit-cancel', this.cancelEdit);
+    // Reopen and close actions for Issue/MR combined with note form submit
+    $(document).on('click', '.js-comment-submit-button', this.postComment);
+    $(document).on('click', '.js-comment-save-button', this.updateComment);
+    $(document).on('keyup input', '.js-note-text', this.updateTargetButtons);
+    // resolve a discussion
+    $(document).on('click', '.js-comment-resolve-button', this.postComment);
+    // remove a note (in general)
+    $(document).on('click', '.js-note-delete', this.removeNote);
+    // delete note attachment
+    $(document).on('click', '.js-note-attachment-delete', this.removeAttachment);
+    // reset main target form when clicking discard
+    $(document).on('click', '.js-note-discard', this.resetMainTargetForm);
+    // update the file name when an attachment is selected
+    $(document).on('change', '.js-note-attachment-input', this.updateFormAttachment);
+    // reply to diff/discussion notes
+    $(document).on('click', '.js-discussion-reply-button', this.onReplyToDiscussionNote);
+    // add diff note
+    $(document).on('click', '.js-add-diff-note-button', this.onAddDiffNote);
+    // hide diff note form
+    $(document).on('click', '.js-close-discussion-note-form', this.cancelDiscussionForm);
+    // toggle commit list
+    $(document).on('click', '.system-note-commit-list-toggler', this.toggleCommitList);
+    // fetch notes when tab becomes visible
+    $(document).on('visibilitychange', this.visibilityChange);
+    // when issue status changes, we need to refresh data
+    $(document).on('issuable:change', this.refresh);
+    // ajax:events that happen on Form when actions like Reopen, Close are performed on Issues and MRs.
+    $(document).on('ajax:success', '.js-main-target-form', this.addNote);
+    $(document).on('ajax:success', '.js-discussion-note-form', this.addDiscussionNote);
+    $(document).on('ajax:success', '.js-main-target-form', this.resetMainTargetForm);
+    $(document).on('ajax:complete', '.js-main-target-form', this.reenableTargetFormSubmitButton);
+    // when a key is clicked on the notes
+    $(document).on('keydown', '.js-note-text', this.keydownNoteText);
+    // When the URL fragment/hash has changed, `#note_xxx`
+    return $(window).on('hashchange', this.onHashChange);
+  }
+
+  cleanBinding() {
+    $(document).off('click', '.js-note-edit');
+    $(document).off('click', '.note-edit-cancel');
+    $(document).off('click', '.js-note-delete');
+    $(document).off('click', '.js-note-attachment-delete');
+    $(document).off('click', '.js-discussion-reply-button');
+    $(document).off('click', '.js-add-diff-note-button');
+    $(document).off('visibilitychange');
+    $(document).off('keyup input', '.js-note-text');
+    $(document).off('click', '.js-note-target-reopen');
+    $(document).off('click', '.js-note-target-close');
+    $(document).off('click', '.js-note-discard');
+    $(document).off('keydown', '.js-note-text');
+    $(document).off('click', '.js-comment-resolve-button');
+    $(document).off('click', '.system-note-commit-list-toggler');
+    $(document).off('ajax:success', '.js-main-target-form');
+    $(document).off('ajax:success', '.js-discussion-note-form');
+    $(document).off('ajax:complete', '.js-main-target-form');
+    $(window).off('hashchange', this.onHashChange);
+  }
+
+  static initCommentTypeToggle(form) {
+    const dropdownTrigger = form.querySelector('.js-comment-type-dropdown .dropdown-toggle');
+    const dropdownList = form.querySelector('.js-comment-type-dropdown .dropdown-menu');
+    const noteTypeInput = form.querySelector('#note_type');
+    const submitButton = form.querySelector('.js-comment-type-dropdown .js-comment-submit-button');
+    const closeButton = form.querySelector('.js-note-target-close');
+    const reopenButton = form.querySelector('.js-note-target-reopen');
+
+    const commentTypeToggle = new CommentTypeToggle({
+      dropdownTrigger,
+      dropdownList,
+      noteTypeInput,
+      submitButton,
+      closeButton,
+      reopenButton,
+    });
+
+    commentTypeToggle.initDroplab();
+  }
+
+  keydownNoteText(e) {
+    var $textarea, discussionNoteForm, editNote, myLastNote, myLastNoteEditBtn, newText, originalText;
+    if (gl.utils.isMetaKey(e)) {
+      return;
     }
 
-    Notes.prototype.setViewType = function(view) {
-      this.view = Cookies.get('diff_view') || view;
-    };
-
-    Notes.prototype.addBinding = function() {
-      // Edit note link
-      $(document).on('click', '.js-note-edit', this.showEditForm.bind(this));
-      $(document).on('click', '.note-edit-cancel', this.cancelEdit);
-      // Reopen and close actions for Issue/MR combined with note form submit
-      $(document).on('click', '.js-comment-submit-button', this.postComment);
-      $(document).on('click', '.js-comment-save-button', this.updateComment);
-      $(document).on('keyup input', '.js-note-text', this.updateTargetButtons);
-      // resolve a discussion
-      $(document).on('click', '.js-comment-resolve-button', this.postComment);
-      // remove a note (in general)
-      $(document).on('click', '.js-note-delete', this.removeNote);
-      // delete note attachment
-      $(document).on('click', '.js-note-attachment-delete', this.removeAttachment);
-      // reset main target form when clicking discard
-      $(document).on('click', '.js-note-discard', this.resetMainTargetForm);
-      // update the file name when an attachment is selected
-      $(document).on('change', '.js-note-attachment-input', this.updateFormAttachment);
-      // reply to diff/discussion notes
-      $(document).on('click', '.js-discussion-reply-button', this.onReplyToDiscussionNote);
-      // add diff note
-      $(document).on('click', '.js-add-diff-note-button', this.onAddDiffNote);
-      // hide diff note form
-      $(document).on('click', '.js-close-discussion-note-form', this.cancelDiscussionForm);
-      // toggle commit list
-      $(document).on('click', '.system-note-commit-list-toggler', this.toggleCommitList);
-      // fetch notes when tab becomes visible
-      $(document).on('visibilitychange', this.visibilityChange);
-      // when issue status changes, we need to refresh data
-      $(document).on('issuable:change', this.refresh);
-      // ajax:events that happen on Form when actions like Reopen, Close are performed on Issues and MRs.
-      $(document).on('ajax:success', '.js-main-target-form', this.addNote);
-      $(document).on('ajax:success', '.js-discussion-note-form', this.addDiscussionNote);
-      $(document).on('ajax:success', '.js-main-target-form', this.resetMainTargetForm);
-      $(document).on('ajax:complete', '.js-main-target-form', this.reenableTargetFormSubmitButton);
-      // when a key is clicked on the notes
-      $(document).on('keydown', '.js-note-text', this.keydownNoteText);
-      // When the URL fragment/hash has changed, `#note_xxx`
-      return $(window).on('hashchange', this.onHashChange);
-    };
-
-    Notes.prototype.cleanBinding = function() {
-      $(document).off('click', '.js-note-edit');
-      $(document).off('click', '.note-edit-cancel');
-      $(document).off('click', '.js-note-delete');
-      $(document).off('click', '.js-note-attachment-delete');
-      $(document).off('click', '.js-discussion-reply-button');
-      $(document).off('click', '.js-add-diff-note-button');
-      $(document).off('visibilitychange');
-      $(document).off('keyup input', '.js-note-text');
-      $(document).off('click', '.js-note-target-reopen');
-      $(document).off('click', '.js-note-target-close');
-      $(document).off('click', '.js-note-discard');
-      $(document).off('keydown', '.js-note-text');
-      $(document).off('click', '.js-comment-resolve-button');
-      $(document).off('click', '.system-note-commit-list-toggler');
-      $(document).off('ajax:success', '.js-main-target-form');
-      $(document).off('ajax:success', '.js-discussion-note-form');
-      $(document).off('ajax:complete', '.js-main-target-form');
-      $(window).off('hashchange', this.onHashChange);
-    };
-
-    Notes.initCommentTypeToggle = function (form) {
-      const dropdownTrigger = form.querySelector('.js-comment-type-dropdown .dropdown-toggle');
-      const dropdownList = form.querySelector('.js-comment-type-dropdown .dropdown-menu');
-      const noteTypeInput = form.querySelector('#note_type');
-      const submitButton = form.querySelector('.js-comment-type-dropdown .js-comment-submit-button');
-      const closeButton = form.querySelector('.js-note-target-close');
-      const reopenButton = form.querySelector('.js-note-target-reopen');
-
-      const commentTypeToggle = new CommentTypeToggle({
-        dropdownTrigger,
-        dropdownList,
-        noteTypeInput,
-        submitButton,
-        closeButton,
-        reopenButton,
-      });
-
-      commentTypeToggle.initDroplab();
-    };
-
-    Notes.prototype.keydownNoteText = function(e) {
-      var $textarea, discussionNoteForm, editNote, myLastNote, myLastNoteEditBtn, newText, originalText;
-      if (gl.utils.isMetaKey(e)) {
-        return;
-      }
-
-      $textarea = $(e.target);
-      // Edit previous note when UP arrow is hit
-      switch (e.which) {
-        case 38:
+    $textarea = $(e.target);
+    // Edit previous note when UP arrow is hit
+    switch (e.which) {
+      case 38:
+        if ($textarea.val() !== '') {
+          return;
+        }
+        myLastNote = $(`li.note[data-author-id='${gon.current_user_id}'][data-editable]:last`, $textarea.closest('.note, .notes_holder, #notes'));
+        if (myLastNote.length) {
+          myLastNoteEditBtn = myLastNote.find('.js-note-edit');
+          return myLastNoteEditBtn.trigger('click', [true, myLastNote]);
+        }
+        break;
+      // Cancel creating diff note or editing any note when ESCAPE is hit
+      case 27:
+        discussionNoteForm = $textarea.closest('.js-discussion-note-form');
+        if (discussionNoteForm.length) {
           if ($textarea.val() !== '') {
-            return;
-          }
-          myLastNote = $(`li.note[data-author-id='${gon.current_user_id}'][data-editable]:last`, $textarea.closest('.note, #notes'));
-          if (myLastNote.length) {
-            myLastNoteEditBtn = myLastNote.find('.js-note-edit');
-            return myLastNoteEditBtn.trigger('click', [true, myLastNote]);
-          }
-          break;
-        // Cancel creating diff note or editing any note when ESCAPE is hit
-        case 27:
-          discussionNoteForm = $textarea.closest('.js-discussion-note-form');
-          if (discussionNoteForm.length) {
-            if ($textarea.val() !== '') {
-              if (!confirm('Are you sure you want to cancel creating this comment?')) {
-                return;
-              }
+            if (!confirm('Are you sure you want to cancel creating this comment?')) {
+              return;
             }
-            this.removeDiscussionNoteForm(discussionNoteForm);
-            return;
           }
-          editNote = $textarea.closest('.note');
-          if (editNote.length) {
-            originalText = $textarea.closest('form').data('original-note');
-            newText = $textarea.val();
-            if (originalText !== newText) {
-              if (!confirm('Are you sure you want to cancel editing this comment?')) {
-                return;
-              }
+          this.removeDiscussionNoteForm(discussionNoteForm);
+          return;
+        }
+        editNote = $textarea.closest('.note');
+        if (editNote.length) {
+          originalText = $textarea.closest('form').data('original-note');
+          newText = $textarea.val();
+          if (originalText !== newText) {
+            if (!confirm('Are you sure you want to cancel editing this comment?')) {
+              return;
             }
-            return this.removeNoteEditForm(editNote);
           }
-      }
-    };
+          return this.removeNoteEditForm(editNote);
+        }
+    }
+  }
 
-    Notes.prototype.initRefresh = function() {
+  initRefresh() {
+    if (Notes.interval) {
       clearInterval(Notes.interval);
-      return Notes.interval = setInterval((function(_this) {
-        return function() {
-          return _this.refresh();
-        };
-      })(this), this.pollingInterval);
-    };
+    }
+    return Notes.interval = setInterval((function(_this) {
+      return function() {
+        return _this.refresh();
+      };
+    })(this), this.pollingInterval);
+  }
 
-    Notes.prototype.refresh = function() {
-      if (!document.hidden) {
-        return this.getContent();
-      }
-    };
+  refresh() {
+    if (!document.hidden) {
+      return this.getContent();
+    }
+  }
 
-    Notes.prototype.getContent = function() {
-      if (this.refreshing) {
-        return;
-      }
-      this.refreshing = true;
-      return $.ajax({
-        url: this.notes_url,
-        headers: { 'X-Last-Fetched-At': this.last_fetched_at },
-        dataType: 'json',
-        success: (function(_this) {
-          return function(data) {
-            var notes;
-            notes = data.notes;
-            _this.last_fetched_at = data.last_fetched_at;
-            _this.setPollingInterval(data.notes.length);
-            return $.each(notes, function(i, note) {
-              _this.renderNote(note);
-            });
-          };
-        })(this)
-      }).always((function(_this) {
-        return function() {
-          return _this.refreshing = false;
+  getContent() {
+    if (this.refreshing) {
+      return;
+    }
+    this.refreshing = true;
+    return $.ajax({
+      url: this.notes_url,
+      headers: { 'X-Last-Fetched-At': this.last_fetched_at },
+      dataType: 'json',
+      success: (function(_this) {
+        return function(data) {
+          var notes;
+          notes = data.notes;
+          _this.last_fetched_at = data.last_fetched_at;
+          _this.setPollingInterval(data.notes.length);
+          return $.each(notes, function(i, note) {
+            _this.renderNote(note);
+          });
         };
-      })(this));
-    };
-
-    /*
-    Increase @pollingInterval up to 120 seconds on every function call,
-    if `shouldReset` has a truthy value, 'null' or 'undefined' the variable
-    will reset to @basePollingInterval.
-
-    Note: this function is used to gradually increase the polling interval
-    if there aren't new notes coming from the server
-     */
-
-    Notes.prototype.setPollingInterval = function(shouldReset) {
-      var nthInterval;
-      if (shouldReset == null) {
-        shouldReset = true;
-      }
-      nthInterval = this.basePollingInterval * Math.pow(2, this.maxPollingSteps - 1);
-      if (shouldReset) {
-        this.pollingInterval = this.basePollingInterval;
-      } else if (this.pollingInterval < nthInterval) {
-        this.pollingInterval *= 2;
-      }
-      return this.initRefresh();
-    };
-
-    Notes.prototype.handleSlashCommands = function(noteEntity) {
-      var votesBlock;
-      if (noteEntity.commands_changes) {
-        if ('merge' in noteEntity.commands_changes) {
-          Notes.checkMergeRequestStatus();
-        }
-
-        if ('emoji_award' in noteEntity.commands_changes) {
-          votesBlock = $('.js-awards-block').eq(0);
-          gl.awardsHandler.addAwardToEmojiBar(votesBlock, noteEntity.commands_changes.emoji_award);
-          return gl.awardsHandler.scrollToAwards();
-        }
+      })(this)
+    }).always((function(_this) {
+      return function() {
+        return _this.refreshing = false;
+      };
+    })(this));
+  }
+
+  /**
+   * Increase @pollingInterval up to 120 seconds on every function call,
+   * if `shouldReset` has a truthy value, 'null' or 'undefined' the variable
+   * will reset to @basePollingInterval.
+   *
+   * Note: this function is used to gradually increase the polling interval
+   * if there aren't new notes coming from the server
+   */
+  setPollingInterval(shouldReset) {
+    var nthInterval;
+    if (shouldReset == null) {
+      shouldReset = true;
+    }
+    nthInterval = this.basePollingInterval * Math.pow(2, this.maxPollingSteps - 1);
+    if (shouldReset) {
+      this.pollingInterval = this.basePollingInterval;
+    } else if (this.pollingInterval < nthInterval) {
+      this.pollingInterval *= 2;
+    }
+    return this.initRefresh();
+  }
+
+  handleQuickActions(noteEntity) {
+    var votesBlock;
+    if (noteEntity.commands_changes) {
+      if ('merge' in noteEntity.commands_changes) {
+        Notes.checkMergeRequestStatus();
       }
-    };
-
-    Notes.prototype.setupNewNote = function($note) {
-      // Update datetime format on the recent note
-      gl.utils.localTimeAgo($note.find('.js-timeago'), false);
 
-      this.collapseLongCommitList();
-      this.taskList.init();
+      if ('emoji_award' in noteEntity.commands_changes) {
+        votesBlock = $('.js-awards-block').eq(0);
 
-      // This stops the note highlight, #note_xxx`, from being removed after real time update
-      // The `:target` selector does not re-evaluate after we replace element in the DOM
-      Notes.updateNoteTargetSelector($note);
-      this.$noteToCleanHighlight = $note;
-    };
-
-    Notes.prototype.onHashChange = function() {
-      if (this.$noteToCleanHighlight) {
-        Notes.updateNoteTargetSelector(this.$noteToCleanHighlight);
+        loadAwardsHandler().then((awardsHandler) => {
+          awardsHandler.addAwardToEmojiBar(votesBlock, noteEntity.commands_changes.emoji_award);
+          awardsHandler.scrollToAwards();
+        }).catch(() => {
+          // ignore
+        });
       }
+    }
+  }
 
-      this.$noteToCleanHighlight = null;
-    };
-
-    Notes.updateNoteTargetSelector = function($note) {
-      const hash = gl.utils.getLocationHash();
-      $note.toggleClass('target', hash && $note.filter(`#${hash}`).length > 0);
-    };
+  setupNewNote($note) {
+    // Update datetime format on the recent note
+    gl.utils.localTimeAgo($note.find('.js-timeago'), false);
 
-    /*
-    Render note in main comments area.
+    this.collapseLongCommitList();
+    this.taskList.init();
 
-    Note: for rendering inline notes use renderDiscussionNote
-     */
+    // This stops the note highlight, #note_xxx`, from being removed after real time update
+    // The `:target` selector does not re-evaluate after we replace element in the DOM
+    Notes.updateNoteTargetSelector($note);
+    this.$noteToCleanHighlight = $note;
+  }
 
-    Notes.prototype.renderNote = function(noteEntity, $form, $notesList = $('.main-notes-list')) {
-      if (noteEntity.discussion_html) {
-        return this.renderDiscussionNote(noteEntity, $form);
-      }
-
-      if (!noteEntity.valid) {
-        if (noteEntity.errors.commands_only) {
-          this.addFlash(noteEntity.errors.commands_only, 'notice', this.parentTimeline);
-          this.refresh();
-        }
-        return;
-      }
+  onHashChange() {
+    if (this.$noteToCleanHighlight) {
+      Notes.updateNoteTargetSelector(this.$noteToCleanHighlight);
+    }
 
-      const $note = $notesList.find(`#note_${noteEntity.id}`);
-      if (Notes.isNewNote(noteEntity, this.note_ids)) {
-        this.note_ids.push(noteEntity.id);
+    this.$noteToCleanHighlight = null;
+  }
+
+  static updateNoteTargetSelector($note) {
+    const hash = gl.utils.getLocationHash();
+    // Needs to be an explicit true/false for the jQuery `toggleClass(force)`
+    const addTargetClass = Boolean(hash && $note.filter(`#${hash}`).length > 0);
+    $note.toggleClass('target', addTargetClass);
+  }
+
+  /**
+   * Render note in main comments area.
+   *
+   * Note: for rendering inline notes use renderDiscussionNote
+   */
+  renderNote(noteEntity, $form, $notesList = $('.main-notes-list')) {
+    if (noteEntity.discussion_html) {
+      return this.renderDiscussionNote(noteEntity, $form);
+    }
 
-        if ($notesList.length) {
+    if (!noteEntity.valid) {
+      if (noteEntity.errors.commands_only) {
+        if (noteEntity.commands_changes &&
+            Object.keys(noteEntity.commands_changes).length > 0) {
           $notesList.find('.system-note.being-posted').remove();
         }
-        const $newNote = Notes.animateAppendNote(noteEntity.html, $notesList);
-
-        this.setupNewNote($newNote);
+        this.addFlash(noteEntity.errors.commands_only, 'notice', this.parentTimeline);
         this.refresh();
-        return this.updateNotesCount(1);
       }
-      // The server can send the same update multiple times so we need to make sure to only update once per actual update.
-      else if (Notes.isUpdatedNote(noteEntity, $note)) {
-        const isEditing = $note.hasClass('is-editing');
-        const initialContent = normalizeNewlines(
-          $note.find('.original-note-content').text().trim()
-        );
-        const $textarea = $note.find('.js-note-text');
-        const currentContent = $textarea.val();
-        // There can be CRLF vs LF mismatches if we don't sanitize and compare the same way
-        const sanitizedNoteNote = normalizeNewlines(noteEntity.note);
-        const isTextareaUntouched = currentContent === initialContent || currentContent === sanitizedNoteNote;
-
-        if (isEditing && isTextareaUntouched) {
-          $textarea.val(noteEntity.note);
-          this.updatedNotesTrackingMap[noteEntity.id] = noteEntity;
-        }
-        else if (isEditing && !isTextareaUntouched) {
-          this.putConflictEditWarningInPlace(noteEntity, $note);
-          this.updatedNotesTrackingMap[noteEntity.id] = noteEntity;
-        }
-        else {
-          const $updatedNote = Notes.animateUpdateNote(noteEntity.html, $note);
-          this.setupNewNote($updatedNote);
-        }
-      }
-    };
-
-    Notes.prototype.isParallelView = function() {
-      return Cookies.get('diff_view') === 'parallel';
-    };
-
-    /*
-    Render note in discussion area.
-
-    Note: for rendering inline notes use renderDiscussionNote
-     */
+      return;
+    }
 
-    Notes.prototype.renderDiscussionNote = function(noteEntity, $form) {
-      var discussionContainer, form, row, lineType, diffAvatarContainer;
-      if (!Notes.isNewNote(noteEntity, this.note_ids)) {
-        return;
-      }
+    const $note = $notesList.find(`#note_${noteEntity.id}`);
+    if (Notes.isNewNote(noteEntity, this.note_ids)) {
       this.note_ids.push(noteEntity.id);
-      form = $form || $(`.js-discussion-note-form[data-discussion-id="${noteEntity.discussion_id}"]`);
-      row = form.closest('tr');
-      lineType = this.isParallelView() ? form.find('#line_type').val() : 'old';
-      diffAvatarContainer = row.prevAll('.line_holder').first().find('.js-avatar-container.' + lineType + '_line');
-      // is this the first note of discussion?
-      discussionContainer = $(`.notes[data-discussion-id="${noteEntity.discussion_id}"]`);
-      if (!discussionContainer.length) {
-        discussionContainer = form.closest('.discussion').find('.notes');
-      }
-      if (discussionContainer.length === 0) {
-        if (noteEntity.diff_discussion_html) {
-          var $discussion = $(noteEntity.diff_discussion_html).renderGFM();
-
-          if (!this.isParallelView() || row.hasClass('js-temp-notes-holder')) {
-            // insert the note and the reply button after the temp row
-            row.after($discussion);
-          } else {
-            // Merge new discussion HTML in
-            var $notes = $discussion.find(`.notes[data-discussion-id="${noteEntity.discussion_id}"]`);
-            var contentContainerClass = '.' + $notes.closest('.notes_content')
-              .attr('class')
-              .split(' ')
-              .join('.');
-
-            row.find(contentContainerClass + ' .content').append($notes.closest('.content').children());
-          }
-        }
-        // Init discussion on 'Discussion' page if it is merge request page
-        const page = $('body').attr('data-page');
-        if ((page && page.indexOf('projects:merge_request') !== -1) || !noteEntity.diff_discussion_html) {
-          Notes.animateAppendNote(noteEntity.discussion_html, $('.main-notes-list'));
-        }
-      } else {
-        // append new note to all matching discussions
-        Notes.animateAppendNote(noteEntity.html, discussionContainer);
-      }
 
-      if (typeof gl.diffNotesCompileComponents !== 'undefined' && noteEntity.discussion_resolvable) {
-        gl.diffNotesCompileComponents();
-        this.renderDiscussionAvatar(diffAvatarContainer, noteEntity);
+      if ($notesList.length) {
+        $notesList.find('.system-note.being-posted').remove();
       }
+      const $newNote = Notes.animateAppendNote(noteEntity.html, $notesList);
 
-      gl.utils.localTimeAgo($('.js-timeago'), false);
-      Notes.checkMergeRequestStatus();
+      this.setupNewNote($newNote);
+      this.refresh();
       return this.updateNotesCount(1);
-    };
-
-    Notes.prototype.getLineHolder = function(changesDiscussionContainer) {
-      return $(changesDiscussionContainer).closest('.notes_holder')
-        .prevAll('.line_holder')
-        .first()
-        .get(0);
-    };
-
-    Notes.prototype.renderDiscussionAvatar = function(diffAvatarContainer, noteEntity) {
-      var commentButton = diffAvatarContainer.find('.js-add-diff-note-button');
-      var avatarHolder = diffAvatarContainer.find('.diff-comment-avatar-holders');
-
-      if (!avatarHolder.length) {
-        avatarHolder = document.createElement('diff-note-avatars');
-        avatarHolder.setAttribute('discussion-id', noteEntity.discussion_id);
-
-        diffAvatarContainer.append(avatarHolder);
+    }
+    // The server can send the same update multiple times so we need to make sure to only update once per actual update.
+    else if (Notes.isUpdatedNote(noteEntity, $note)) {
+      const isEditing = $note.hasClass('is-editing');
+      const initialContent = normalizeNewlines(
+        $note.find('.original-note-content').text().trim()
+      );
+      const $textarea = $note.find('.js-note-text');
+      const currentContent = $textarea.val();
+      // There can be CRLF vs LF mismatches if we don't sanitize and compare the same way
+      const sanitizedNoteNote = normalizeNewlines(noteEntity.note);
+      const isTextareaUntouched = currentContent === initialContent || currentContent === sanitizedNoteNote;
 
-        gl.diffNotesCompileComponents();
+      if (isEditing && isTextareaUntouched) {
+        $textarea.val(noteEntity.note);
+        this.updatedNotesTrackingMap[noteEntity.id] = noteEntity;
       }
-
-      if (commentButton.length) {
-        commentButton.remove();
+      else if (isEditing && !isTextareaUntouched) {
+        this.putConflictEditWarningInPlace(noteEntity, $note);
+        this.updatedNotesTrackingMap[noteEntity.id] = noteEntity;
       }
-    };
-
-    /*
-    Called in response the main target form has been successfully submitted.
-
-    Removes any errors.
-    Resets text and preview.
-    Resets buttons.
-     */
-
-    Notes.prototype.resetMainTargetForm = function(e) {
-      var form;
-      form = $('.js-main-target-form');
-      // remove validation errors
-      form.find('.js-errors').remove();
-      // reset text and preview
-      form.find('.js-md-write-button').click();
-      form.find('.js-note-text').val('').trigger('input');
-      form.find('.js-note-text').data('autosave').reset();
-
-      var event = document.createEvent('Event');
-      event.initEvent('autosize:update', true, false);
-      form.find('.js-autosize')[0].dispatchEvent(event);
-
-      this.updateTargetButtons(e);
-    };
-
-    Notes.prototype.reenableTargetFormSubmitButton = function() {
-      var form;
-      form = $('.js-main-target-form');
-      return form.find('.js-note-text').trigger('input');
-    };
-
-    /*
-    Shows the main form and does some setup on it.
-
-    Sets some hidden fields in the form.
-     */
-
-    Notes.prototype.setupMainTargetNoteForm = function() {
-      var form;
-      // find the form
-      form = $('.js-new-note-form');
-      // Set a global clone of the form for later cloning
-      this.formClone = form.clone();
-      // show the form
-      this.setupNoteForm(form);
-      // fix classes
-      form.removeClass('js-new-note-form');
-      form.addClass('js-main-target-form');
-      form.find('#note_line_code').remove();
-      form.find('#note_position').remove();
-      form.find('#note_type').val('');
-      form.find('#in_reply_to_discussion_id').remove();
-      form.find('.js-comment-resolve-button').closest('comment-and-resolve-btn').remove();
-      this.parentTimeline = form.parents('.timeline');
-
-      if (form.length) {
-        Notes.initCommentTypeToggle(form.get(0));
-      }
-    };
-
-    /*
-    General note form setup.
-
-    deactivates the submit button when text is empty
-    hides the preview button when text is empty
-    setup GFM auto complete
-    show the form
-     */
-
-    Notes.prototype.setupNoteForm = function(form) {
-      var textarea, key;
-      new gl.GLForm(form, this.enableGFM);
-      textarea = form.find('.js-note-text');
-      key = [
-        '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('#in_reply_to_discussion_id').val(),
-
-        // LegacyDiffNote
-        form.find('#note_line_code').val(),
-
-        // DiffNote
-        form.find('#note_position').val()
-      ];
-      return new Autosave(textarea, key);
-    };
-
-    /*
-    Called in response to the new note form being submitted
-
-    Adds new note to list.
-     */
-
-    Notes.prototype.addNote = function($form, note) {
-      return this.renderNote(note);
-    };
-
-    Notes.prototype.addNoteError = function($form) {
-      let formParentTimeline;
-      if ($form.hasClass('js-main-target-form')) {
-        formParentTimeline = $form.parents('.timeline');
-      } else if ($form.hasClass('js-discussion-note-form')) {
-        formParentTimeline = $form.closest('.discussion-notes').find('.notes');
+      else {
+        const $updatedNote = Notes.animateUpdateNote(noteEntity.html, $note);
+        this.setupNewNote($updatedNote);
       }
-      return this.addFlash('Your comment could not be submitted! Please check your network connection and try again.', 'alert', formParentTimeline);
-    };
-
-    Notes.prototype.updateNoteError = $parentTimeline => new Flash('Your comment could not be updated! Please check your network connection and try again.');
-
-    /*
-    Called in response to the new note form being submitted
-
-    Adds new note to list.
-     */
-
-    Notes.prototype.addDiscussionNote = function($form, note, isNewDiffComment) {
-      if ($form.attr('data-resolve-all') != null) {
-        var projectPath = $form.data('project-path');
-        var discussionId = $form.data('discussion-id');
-        var mergeRequestId = $form.data('noteable-iid');
+    }
+  }
+
+  isParallelView() {
+    return Cookies.get('diff_view') === 'parallel';
+  }
+
+  /**
+   * Render note in discussion area.
+   *
+   * Note: for rendering inline notes use renderDiscussionNote
+   */
+  renderDiscussionNote(noteEntity, $form) {
+    var discussionContainer, form, row, lineType, diffAvatarContainer;
+    if (!Notes.isNewNote(noteEntity, this.note_ids)) {
+      return;
+    }
+    this.note_ids.push(noteEntity.id);
+    form = $form || $(`.js-discussion-note-form[data-discussion-id="${noteEntity.discussion_id}"]`);
+    row = form.closest('tr');
+    lineType = this.isParallelView() ? form.find('#line_type').val() : 'old';
+    diffAvatarContainer = row.prevAll('.line_holder').first().find('.js-avatar-container.' + lineType + '_line');
+    // is this the first note of discussion?
+    discussionContainer = $(`.notes[data-discussion-id="${noteEntity.discussion_id}"]`);
+    if (!discussionContainer.length) {
+      discussionContainer = form.closest('.discussion').find('.notes');
+    }
+    if (discussionContainer.length === 0) {
+      if (noteEntity.diff_discussion_html) {
+        var $discussion = $(noteEntity.diff_discussion_html).renderGFM();
 
-        if (ResolveService != null) {
-          ResolveService.toggleResolveForDiscussion(mergeRequestId, discussionId);
+        if (!this.isParallelView() || row.hasClass('js-temp-notes-holder')) {
+          // insert the note and the reply button after the temp row
+          row.after($discussion);
+        } else {
+          // Merge new discussion HTML in
+          var $notes = $discussion.find(`.notes[data-discussion-id="${noteEntity.discussion_id}"]`);
+          var contentContainerClass = '.' + $notes.closest('.notes_content')
+            .attr('class')
+            .split(' ')
+            .join('.');
+
+          row.find(contentContainerClass + ' .content').append($notes.closest('.content').children());
         }
       }
-
-      this.renderNote(note, $form);
-      // cleanup after successfully creating a diff/discussion note
-      if (isNewDiffComment) {
-        this.removeDiscussionNoteForm($form);
+      // Init discussion on 'Discussion' page if it is merge request page
+      const page = $('body').attr('data-page');
+      if ((page && page.indexOf('projects:merge_request') !== -1) || !noteEntity.diff_discussion_html) {
+        Notes.animateAppendNote(noteEntity.discussion_html, $('.main-notes-list'));
       }
-    };
+    } else {
+      // append new note to all matching discussions
+      Notes.animateAppendNote(noteEntity.html, discussionContainer);
+    }
 
-    /*
-    Called in response to the edit note form being submitted
+    if (typeof gl.diffNotesCompileComponents !== 'undefined' && noteEntity.discussion_resolvable) {
+      gl.diffNotesCompileComponents();
+      this.renderDiscussionAvatar(diffAvatarContainer, noteEntity);
+    }
 
-    Updates the current note field.
-     */
+    gl.utils.localTimeAgo($('.js-timeago'), false);
+    Notes.checkMergeRequestStatus();
+    return this.updateNotesCount(1);
+  }
 
-    Notes.prototype.updateNote = function(noteEntity, $targetNote) {
-      var $noteEntityEl, $note_li;
-      // Convert returned HTML to a jQuery object so we can modify it further
-      $noteEntityEl = $(noteEntity.html);
-      $noteEntityEl.addClass('fade-in-full');
-      this.revertNoteEditForm($targetNote);
-      $noteEntityEl.renderGFM();
-      // Find the note's `li` element by ID and replace it with the updated HTML
-      $note_li = $('.note-row-' + noteEntity.id);
+  getLineHolder(changesDiscussionContainer) {
+    return $(changesDiscussionContainer).closest('.notes_holder')
+      .prevAll('.line_holder')
+      .first()
+      .get(0);
+  }
 
-      $note_li.replaceWith($noteEntityEl);
-      this.setupNewNote($noteEntityEl);
+  renderDiscussionAvatar(diffAvatarContainer, noteEntity) {
+    var commentButton = diffAvatarContainer.find('.js-add-diff-note-button');
+    var avatarHolder = diffAvatarContainer.find('.diff-comment-avatar-holders');
 
-      if (typeof gl.diffNotesCompileComponents !== 'undefined') {
-        gl.diffNotesCompileComponents();
-      }
-    };
+    if (!avatarHolder.length) {
+      avatarHolder = document.createElement('diff-note-avatars');
+      avatarHolder.setAttribute('discussion-id', noteEntity.discussion_id);
 
-    Notes.prototype.checkContentToAllowEditing = function($el) {
-      var initialContent = $el.find('.original-note-content').text().trim();
-      var currentContent = $el.find('.js-note-text').val();
-      var isAllowed = true;
+      diffAvatarContainer.append(avatarHolder);
 
-      if (currentContent === initialContent) {
-        this.removeNoteEditForm($el);
-      }
-      else {
-        var $buttons = $el.find('.note-form-actions');
-        var isWidgetVisible = gl.utils.isInViewport($el.get(0));
-
-        if (!isWidgetVisible) {
-          gl.utils.scrollToElement($el);
-        }
+      gl.diffNotesCompileComponents();
+    }
 
-        $el.find('.js-finish-edit-warning').show();
-        isAllowed = false;
-      }
+    if (commentButton.length) {
+      commentButton.remove();
+    }
+  }
+
+  /**
+   * Called in response the main target form has been successfully submitted.
+   *
+   * Removes any errors.
+   * Resets text and preview.
+   * Resets buttons.
+   */
+  resetMainTargetForm(e) {
+    var form;
+    form = $('.js-main-target-form');
+    // remove validation errors
+    form.find('.js-errors').remove();
+    // reset text and preview
+    form.find('.js-md-write-button').click();
+    form.find('.js-note-text').val('').trigger('input');
+    form.find('.js-note-text').data('autosave').reset();
+
+    var event = document.createEvent('Event');
+    event.initEvent('autosize:update', true, false);
+    form.find('.js-autosize')[0].dispatchEvent(event);
+
+    this.updateTargetButtons(e);
+  }
+
+  reenableTargetFormSubmitButton() {
+    var form;
+    form = $('.js-main-target-form');
+    return form.find('.js-note-text').trigger('input');
+  }
+
+  /**
+   * Shows the main form and does some setup on it.
+   *
+   * Sets some hidden fields in the form.
+   */
+  setupMainTargetNoteForm() {
+    var form;
+    // find the form
+    form = $('.js-new-note-form');
+    // Set a global clone of the form for later cloning
+    this.formClone = form.clone();
+    // show the form
+    this.setupNoteForm(form);
+    // fix classes
+    form.removeClass('js-new-note-form');
+    form.addClass('js-main-target-form');
+    form.find('#note_line_code').remove();
+    form.find('#note_position').remove();
+    form.find('#note_type').val('');
+    form.find('#in_reply_to_discussion_id').remove();
+    form.find('.js-comment-resolve-button').closest('comment-and-resolve-btn').remove();
+    this.parentTimeline = form.parents('.timeline');
+
+    if (form.length) {
+      Notes.initCommentTypeToggle(form.get(0));
+    }
+  }
+
+  /**
+   * General note form setup.
+   *
+   * deactivates the submit button when text is empty
+   * hides the preview button when text is empty
+   * setup GFM auto complete
+   * show the form
+   */
+  setupNoteForm(form) {
+    var textarea, key;
+    new gl.GLForm(form, this.enableGFM);
+    textarea = form.find('.js-note-text');
+    key = [
+      '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('#in_reply_to_discussion_id').val(),
 
-      return isAllowed;
-    };
+      // LegacyDiffNote
+      form.find('#note_line_code').val(),
 
-    /*
-    Called in response to clicking the edit note link
+      // DiffNote
+      form.find('#note_position').val()
+    ];
+    return new Autosave(textarea, key);
+  }
+
+  /**
+   * Called in response to the new note form being submitted
+   *
+   * Adds new note to list.
+   */
+  addNote($form, note) {
+    return this.renderNote(note);
+  }
+
+  addNoteError($form) {
+    let formParentTimeline;
+    if ($form.hasClass('js-main-target-form')) {
+      formParentTimeline = $form.parents('.timeline');
+    } else if ($form.hasClass('js-discussion-note-form')) {
+      formParentTimeline = $form.closest('.discussion-notes').find('.notes');
+    }
+    return this.addFlash('Your comment could not be submitted! Please check your network connection and try again.', 'alert', formParentTimeline);
+  }
+
+  updateNoteError($parentTimeline) {
+    new Flash('Your comment could not be updated! Please check your network connection and try again.');
+  }
+
+  /**
+   * Called in response to the new note form being submitted
+   *
+   * Adds new note to list.
+   */
+  addDiscussionNote($form, note, isNewDiffComment) {
+    if ($form.attr('data-resolve-all') != null) {
+      var projectPath = $form.data('project-path');
+      var discussionId = $form.data('discussion-id');
+      var mergeRequestId = $form.data('noteable-iid');
+
+      if (ResolveService != null) {
+        ResolveService.toggleResolveForDiscussion(mergeRequestId, discussionId);
+      }
+    }
 
-    Replaces the note text with the note edit form
-    Adds a data attribute to the form with the original content of the note for cancellations
-    */
-    Notes.prototype.showEditForm = function(e, scrollTo, myLastNote) {
-      e.preventDefault();
+    this.renderNote(note, $form);
+    // cleanup after successfully creating a diff/discussion note
+    if (isNewDiffComment) {
+      this.removeDiscussionNoteForm($form);
+    }
+  }
+
+  /**
+   * Called in response to the edit note form being submitted
+   *
+   * Updates the current note field.
+   */
+  updateNote(noteEntity, $targetNote) {
+    var $noteEntityEl, $note_li;
+    // Convert returned HTML to a jQuery object so we can modify it further
+    $noteEntityEl = $(noteEntity.html);
+    $noteEntityEl.addClass('fade-in-full');
+    this.revertNoteEditForm($targetNote);
+    $noteEntityEl.renderGFM();
+    // Find the note's `li` element by ID and replace it with the updated HTML
+    $note_li = $('.note-row-' + noteEntity.id);
+
+    $note_li.replaceWith($noteEntityEl);
+    this.setupNewNote($noteEntityEl);
+
+    if (typeof gl.diffNotesCompileComponents !== 'undefined') {
+      gl.diffNotesCompileComponents();
+    }
+  }
 
-      var $target = $(e.target);
-      var $editForm = $(this.getEditFormSelector($target));
-      var $note = $target.closest('.note');
-      var $currentlyEditing = $('.note.is-editing:visible');
+  checkContentToAllowEditing($el) {
+    var initialContent = $el.find('.original-note-content').text().trim();
+    var currentContent = $el.find('.js-note-text').val();
+    var isAllowed = true;
 
-      if ($currentlyEditing.length) {
-        var isEditAllowed = this.checkContentToAllowEditing($currentlyEditing);
+    if (currentContent === initialContent) {
+      this.removeNoteEditForm($el);
+    }
+    else {
+      var $buttons = $el.find('.note-form-actions');
+      var isWidgetVisible = gl.utils.isInViewport($el.get(0));
 
-        if (!isEditAllowed) {
-          return;
-        }
+      if (!isWidgetVisible) {
+        gl.utils.scrollToElement($el);
       }
 
-      $note.find('.js-note-attachment-delete').show();
-      $editForm.addClass('current-note-edit-form');
-      $note.addClass('is-editing');
-      this.putEditFormInPlace($target);
-    };
+      $el.find('.js-finish-edit-warning').show();
+      isAllowed = false;
+    }
 
-    /*
-    Called in response to clicking the edit note link
+    return isAllowed;
+  }
 
-    Hides edit form and restores the original note text to the editor textarea.
-     */
+  /**
+   * Called in response to clicking the edit note link
+   *
+   * Replaces the note text with the note edit form
+   * Adds a data attribute to the form with the original content of the note for cancellations
+   */
+  showEditForm(e, scrollTo, myLastNote) {
+    e.preventDefault();
 
-    Notes.prototype.cancelEdit = function(e) {
-      e.preventDefault();
-      const $target = $(e.target);
-      const $note = $target.closest('.note');
-      const noteId = $note.attr('data-note-id');
+    var $target = $(e.target);
+    var $editForm = $(this.getEditFormSelector($target));
+    var $note = $target.closest('.note');
+    var $currentlyEditing = $('.note.is-editing:visible');
 
-      this.revertNoteEditForm($target);
+    if ($currentlyEditing.length) {
+      var isEditAllowed = this.checkContentToAllowEditing($currentlyEditing);
 
-      if (this.updatedNotesTrackingMap[noteId]) {
-        const $newNote = $(this.updatedNotesTrackingMap[noteId].html);
-        $note.replaceWith($newNote);
-        this.setupNewNote($newNote);
-        // Now that we have taken care of the update, clear it out
-        delete this.updatedNotesTrackingMap[noteId];
-      }
-      else {
-        $note.find('.js-finish-edit-warning').hide();
-        this.removeNoteEditForm($note);
+      if (!isEditAllowed) {
+        return;
       }
-    };
-
-    Notes.prototype.revertNoteEditForm = function($target) {
-      $target = $target || $('.note.is-editing:visible');
-      var selector = this.getEditFormSelector($target);
-      var $editForm = $(selector);
+    }
 
-      $editForm.insertBefore('.notes-form');
-      $editForm.find('.js-comment-save-button').enable();
-      $editForm.find('.js-finish-edit-warning').hide();
-    };
+    $note.find('.js-note-attachment-delete').show();
+    $editForm.addClass('current-note-edit-form');
+    $note.addClass('is-editing');
+    this.putEditFormInPlace($target);
+  }
+
+  /**
+   * Called in response to clicking the edit note link
+   *
+   * Hides edit form and restores the original note text to the editor textarea.
+   */
+  cancelEdit(e) {
+    e.preventDefault();
+    const $target = $(e.target);
+    const $note = $target.closest('.note');
+    const noteId = $note.attr('data-note-id');
+
+    this.revertNoteEditForm($target);
+
+    if (this.updatedNotesTrackingMap[noteId]) {
+      const $newNote = $(this.updatedNotesTrackingMap[noteId].html);
+      $note.replaceWith($newNote);
+      this.setupNewNote($newNote);
+      // Now that we have taken care of the update, clear it out
+      delete this.updatedNotesTrackingMap[noteId];
+    }
+    else {
+      $note.find('.js-finish-edit-warning').hide();
+      this.removeNoteEditForm($note);
+    }
+  }
 
-    Notes.prototype.getEditFormSelector = function($el) {
-      var selector = '.note-edit-form:not(.mr-note-edit-form)';
+  revertNoteEditForm($target) {
+    $target = $target || $('.note.is-editing:visible');
+    var selector = this.getEditFormSelector($target);
+    var $editForm = $(selector);
 
-      if ($el.parents('#diffs').length) {
-        selector = '.note-edit-form.mr-note-edit-form';
-      }
+    $editForm.insertBefore('.notes-form');
+    $editForm.find('.js-comment-save-button').enable();
+    $editForm.find('.js-finish-edit-warning').hide();
+  }
 
-      return selector;
-    };
+  getEditFormSelector($el) {
+    var selector = '.note-edit-form:not(.mr-note-edit-form)';
 
-    Notes.prototype.removeNoteEditForm = function($note) {
-      var form = $note.find('.current-note-edit-form');
-      $note.removeClass('is-editing');
-      form.removeClass('current-note-edit-form');
-      form.find('.js-finish-edit-warning').hide();
-      // Replace markdown textarea text with original note text.
-      return form.find('.js-note-text').val(form.find('form.edit-note').data('original-note'));
-    };
+    if ($el.parents('#diffs').length) {
+      selector = '.note-edit-form.mr-note-edit-form';
+    }
 
-    /*
-    Called in response to deleting a note of any kind.
-
-    Removes the actual note from view.
-    Removes the whole discussion if the last note is being removed.
-     */
-
-    Notes.prototype.removeNote = function(e) {
-      var noteElId, noteId, dataNoteId, $note, lineHolder;
-      $note = $(e.currentTarget).closest('.note');
-      noteElId = $note.attr('id');
-      noteId = $note.attr('data-note-id');
-      lineHolder = $(e.currentTarget).closest('.notes[data-discussion-id]')
-        .closest('.notes_holder')
-        .prev('.line_holder');
-      $(`.note[id="${noteElId}"]`).each((function(_this) {
-        // A same note appears in the "Discussion" and in the "Changes" tab, we have
-        // to remove all. Using $('.note[id='noteId']') ensure we get all the notes,
-        // where $('#noteId') would return only one.
-        return function(i, el) {
-          var $note, $notes;
-          $note = $(el);
-          $notes = $note.closest('.discussion-notes');
-
-          if (typeof gl.diffNotesCompileComponents !== 'undefined') {
-            if (gl.diffNoteApps[noteElId]) {
-              gl.diffNoteApps[noteElId].$destroy();
-            }
+    return selector;
+  }
+
+  removeNoteEditForm($note) {
+    var form = $note.find('.current-note-edit-form');
+    $note.removeClass('is-editing');
+    form.removeClass('current-note-edit-form');
+    form.find('.js-finish-edit-warning').hide();
+    // Replace markdown textarea text with original note text.
+    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.
+   *
+   * Removes the actual note from view.
+   * Removes the whole discussion if the last note is being removed.
+   */
+  removeNote(e) {
+    var noteElId, noteId, dataNoteId, $note, lineHolder;
+    $note = $(e.currentTarget).closest('.note');
+    noteElId = $note.attr('id');
+    noteId = $note.attr('data-note-id');
+    lineHolder = $(e.currentTarget).closest('.notes[data-discussion-id]')
+      .closest('.notes_holder')
+      .prev('.line_holder');
+    $(`.note[id="${noteElId}"]`).each((function(_this) {
+      // A same note appears in the "Discussion" and in the "Changes" tab, we have
+      // to remove all. Using $('.note[id='noteId']') ensure we get all the notes,
+      // where $('#noteId') would return only one.
+      return function(i, el) {
+        var $note, $notes;
+        $note = $(el);
+        $notes = $note.closest('.discussion-notes');
+
+        if (typeof gl.diffNotesCompileComponents !== 'undefined') {
+          if (gl.diffNoteApps[noteElId]) {
+            gl.diffNoteApps[noteElId].$destroy();
           }
+        }
 
-          $note.remove();
+        $note.remove();
 
-          // check if this is the last note for this line
-          if ($notes.find('.note').length === 0) {
-            var notesTr = $notes.closest('tr');
+        // check if this is the last note for this line
+        if ($notes.find('.note').length === 0) {
+          var notesTr = $notes.closest('tr');
 
-            // "Discussions" tab
-            $notes.closest('.timeline-entry').remove();
+          // "Discussions" tab
+          $notes.closest('.timeline-entry').remove();
 
-            // The notes tr can contain multiple lists of notes, like on the parallel diff
-            if (notesTr.find('.discussion-notes').length > 1) {
-              $notes.remove();
-            } else {
-              notesTr.remove();
-            }
+          // The notes tr can contain multiple lists of notes, like on the parallel diff
+          if (notesTr.find('.discussion-notes').length > 1) {
+            $notes.remove();
+          } else {
+            notesTr.remove();
           }
-        };
-      })(this));
-
-      Notes.checkMergeRequestStatus();
-      return this.updateNotesCount(-1);
-    };
-
-    /*
-    Called in response to clicking the delete attachment link
+        }
+      };
+    })(this));
+
+    Notes.checkMergeRequestStatus();
+    return this.updateNotesCount(-1);
+  }
+
+  /**
+   * Called in response to clicking the delete attachment link
+   *
+   * Removes the attachment wrapper view, including image tag if it exists
+   * Resets the note editing form
+   */
+  removeAttachment() {
+    const $note = $(this).closest('.note');
+    $note.find('.note-attachment').remove();
+    $note.find('.note-body > .note-text').show();
+    $note.find('.note-header').show();
+    return $note.find('.current-note-edit-form').remove();
+  }
+
+  /**
+   * Called when clicking on the "reply" button for a diff line.
+   *
+   * Shows the note form below the notes.
+   */
+  onReplyToDiscussionNote(e) {
+    this.replyToDiscussionNote(e.target);
+  }
+
+  replyToDiscussionNote(target) {
+    var form, replyLink;
+    form = this.cleanForm(this.formClone.clone());
+    replyLink = $(target).closest('.js-discussion-reply-button');
+    // insert the form after the button
+    replyLink
+      .closest('.discussion-reply-holder')
+      .hide()
+      .after(form);
+    // show the form
+    return this.setupDiscussionNoteForm(replyLink, form);
+  }
+
+  /**
+   * Shows the diff or discussion form and does some setup on it.
+   *
+   * Sets some hidden fields in the form.
+   *
+   * Note: dataHolder must have the "discussionId" and "lineCode" data attributes set.
+   */
+  setupDiscussionNoteForm(dataHolder, form) {
+    // setup note target
+    const diffFileData = dataHolder.closest('.text-file');
+
+    var discussionID = dataHolder.data('discussionId');
+
+    if (discussionID) {
+      form.attr('data-discussion-id', discussionID);
+      form.find('#in_reply_to_discussion_id').val(discussionID);
+    }
 
-    Removes the attachment wrapper view, including image tag if it exists
-    Resets the note editing form
-     */
+    form.attr('data-line-code', dataHolder.data('lineCode'));
+    form.find('#line_type').val(dataHolder.data('lineType'));
 
-    Notes.prototype.removeAttachment = function() {
-      const $note = $(this).closest('.note');
-      $note.find('.note-attachment').remove();
-      $note.find('.note-body > .note-text').show();
-      $note.find('.note-header').show();
-      return $note.find('.current-note-edit-form').remove();
-    };
+    form.find('#note_noteable_type').val(diffFileData.data('noteableType'));
+    form.find('#note_noteable_id').val(diffFileData.data('noteableId'));
+    form.find('#note_commit_id').val(diffFileData.data('commitId'));
 
-    /*
-    Called when clicking on the "reply" button for a diff line.
+    form.find('#note_type').val(dataHolder.data('noteType'));
 
-    Shows the note form below the notes.
-     */
+    // LegacyDiffNote
+    form.find('#note_line_code').val(dataHolder.data('lineCode'));
 
-    Notes.prototype.onReplyToDiscussionNote = function(e) {
-      this.replyToDiscussionNote(e.target);
-    };
+    // DiffNote
+    form.find('#note_position').val(dataHolder.attr('data-position'));
 
-    Notes.prototype.replyToDiscussionNote = function(target) {
-      var form, replyLink;
-      form = this.cleanForm(this.formClone.clone());
-      replyLink = $(target).closest('.js-discussion-reply-button');
-      // insert the form after the button
-      replyLink
-        .closest('.discussion-reply-holder')
-        .hide()
-        .after(form);
-      // show the form
-      return this.setupDiscussionNoteForm(replyLink, form);
-    };
+    form.find('.js-note-discard').show().removeClass('js-note-discard').addClass('js-close-discussion-note-form').text(form.find('.js-close-discussion-note-form').data('cancel-text'));
+    form.find('.js-note-target-close').remove();
+    form.find('.js-note-new-discussion').remove();
+    this.setupNoteForm(form);
 
-    /*
-    Shows the diff or discussion form and does some setup on it.
+    form
+      .removeClass('js-main-target-form')
+      .addClass('discussion-form js-discussion-note-form');
 
-    Sets some hidden fields in the form.
+    if (typeof gl.diffNotesCompileComponents !== 'undefined') {
+      var $commentBtn = form.find('comment-and-resolve-btn');
+      $commentBtn.attr(':discussion-id', `'${discussionID}'`);
 
-    Note: dataHolder must have the "discussionId" and "lineCode" data attributes set.
-     */
+      gl.diffNotesCompileComponents();
+    }
 
-    Notes.prototype.setupDiscussionNoteForm = function(dataHolder, form) {
-      // setup note target
-      var discussionID = dataHolder.data('discussionId');
+    form.find('.js-note-text').focus();
+    form
+      .find('.js-comment-resolve-button')
+      .attr('data-discussion-id', discussionID);
+  }
+
+  /**
+   * Called when clicking on the "add a comment" button on the side of a diff line.
+   *
+   * Inserts a temporary row for the form below the line.
+   * Sets up the form and shows it.
+   */
+  onAddDiffNote(e) {
+    e.preventDefault();
+    const link = e.currentTarget || e.target;
+    const $link = $(link);
+    const showReplyInput = !$link.hasClass('js-diff-comment-avatar');
+    this.toggleDiffNote({
+      target: $link,
+      lineType: link.dataset.lineType,
+      showReplyInput
+    });
+  }
+
+  toggleDiffNote({
+    target,
+    lineType,
+    forceShow,
+    showReplyInput = false,
+  }) {
+    var $link, addForm, hasNotes, newForm, noteForm, replyButton, row, rowCssToAdd, targetContent, isDiffCommentAvatar;
+    $link = $(target);
+    row = $link.closest('tr');
+    const nextRow = row.next();
+    let targetRow = row;
+    if (nextRow.is('.notes_holder')) {
+      targetRow = nextRow;
+    }
 
-      if (discussionID) {
-        form.attr('data-discussion-id', discussionID);
-        form.find('#in_reply_to_discussion_id').val(discussionID);
+    hasNotes = nextRow.is('.notes_holder');
+    addForm = false;
+    let lineTypeSelector = '';
+    rowCssToAdd = '<tr class="notes_holder js-temp-notes-holder"><td class="notes_line" colspan="2"></td><td class="notes_content"><div class="content"></div></td></tr>';
+    // In parallel view, look inside the correct left/right pane
+    if (this.isParallelView()) {
+      lineTypeSelector = `.${lineType}`;
+      rowCssToAdd = '<tr class="notes_holder js-temp-notes-holder"><td class="notes_line old"></td><td class="notes_content parallel old"><div class="content"></div></td><td class="notes_line new"></td><td class="notes_content parallel new"><div class="content"></div></td></tr>';
+    }
+    const notesContentSelector = `.notes_content${lineTypeSelector} .content`;
+    let notesContent = targetRow.find(notesContentSelector);
+
+    if (hasNotes && showReplyInput) {
+      targetRow.show();
+      notesContent = targetRow.find(notesContentSelector);
+      if (notesContent.length) {
+        notesContent.show();
+        replyButton = notesContent.find('.js-discussion-reply-button:visible');
+        if (replyButton.length) {
+          this.replyToDiscussionNote(replyButton[0]);
+        } else {
+          // In parallel view, the form may not be present in one of the panes
+          noteForm = notesContent.find('.js-discussion-note-form');
+          if (noteForm.length === 0) {
+            addForm = true;
+          }
+        }
       }
+    } else if (showReplyInput) {
+      // add a notes row and insert the form
+      row.after(rowCssToAdd);
+      targetRow = row.next();
+      notesContent = targetRow.find(notesContentSelector);
+      addForm = true;
+    } else {
+      const isCurrentlyShown = targetRow.find('.content:not(:empty)').is(':visible');
+      const isForced = forceShow === true || forceShow === false;
+      const showNow = forceShow === true || (!isCurrentlyShown && !isForced);
+
+      targetRow.toggle(showNow);
+      notesContent.toggle(showNow);
+    }
 
-      form.attr('data-line-code', dataHolder.data('lineCode'));
-      form.find('#line_type').val(dataHolder.data('lineType'));
-
-      form.find('#note_noteable_type').val(dataHolder.data('noteableType'));
-      form.find('#note_noteable_id').val(dataHolder.data('noteableId'));
-      form.find('#note_commit_id').val(dataHolder.data('commitId'));
-      form.find('#note_type').val(dataHolder.data('noteType'));
-
-      // LegacyDiffNote
-      form.find('#note_line_code').val(dataHolder.data('lineCode'));
-
-      // DiffNote
-      form.find('#note_position').val(dataHolder.attr('data-position'));
-
-      form.find('.js-note-discard').show().removeClass('js-note-discard').addClass('js-close-discussion-note-form').text(form.find('.js-close-discussion-note-form').data('cancel-text'));
-      form.find('.js-note-target-close').remove();
-      form.find('.js-note-new-discussion').remove();
-      this.setupNoteForm(form);
-
-      form
-        .removeClass('js-main-target-form')
-        .addClass('discussion-form js-discussion-note-form');
-
-      if (typeof gl.diffNotesCompileComponents !== 'undefined') {
-        var $commentBtn = form.find('comment-and-resolve-btn');
-        $commentBtn.attr(':discussion-id', `'${discussionID}'`);
-
-        gl.diffNotesCompileComponents();
+    if (addForm) {
+      newForm = this.cleanForm(this.formClone.clone());
+      newForm.appendTo(notesContent);
+      // show the form
+      return this.setupDiscussionNoteForm($link, newForm);
+    }
+  }
+
+  /**
+   * Called in response to "cancel" on a diff note form.
+   *
+   * Shows the reply button again.
+   * Removes the form and if necessary it's temporary row.
+   */
+  removeDiscussionNoteForm(form) {
+    var glForm, row;
+    row = form.closest('tr');
+    glForm = form.data('gl-form');
+    glForm.destroy();
+    form.find('.js-note-text').data('autosave').reset();
+    // show the reply button (will only work for replies)
+    form
+      .prev('.discussion-reply-holder')
+      .show();
+    if (row.is('.js-temp-notes-holder')) {
+      // remove temporary row for diff lines
+      return row.remove();
+    } else {
+      // only remove the form
+      return form.remove();
+    }
+  }
+
+  cancelDiscussionForm(e) {
+    var form;
+    e.preventDefault();
+    form = $(e.target).closest('.js-discussion-note-form');
+    return this.removeDiscussionNoteForm(form);
+  }
+
+  /**
+   * Called after an attachment file has been selected.
+   *
+   * Updates the file name for the selected attachment.
+   */
+  updateFormAttachment() {
+    var filename, form;
+    form = $(this).closest('form');
+    // get only the basename
+    filename = $(this).val().replace(/^.*[\\\/]/, '');
+    return form.find('.js-attachment-filename').text(filename);
+  }
+
+  /**
+   * Called when the tab visibility changes
+   */
+  visibilityChange() {
+    return this.refresh();
+  }
+
+  updateTargetButtons(e) {
+    var closebtn, closetext, discardbtn, form, reopenbtn, reopentext, textarea;
+    textarea = $(e.target);
+    form = textarea.parents('form');
+    reopenbtn = form.find('.js-note-target-reopen');
+    closebtn = form.find('.js-note-target-close');
+    discardbtn = form.find('.js-note-discard');
+
+    if (textarea.val().trim().length > 0) {
+      reopentext = reopenbtn.attr('data-alternative-text');
+      closetext = closebtn.attr('data-alternative-text');
+      if (reopenbtn.text() !== reopentext) {
+        reopenbtn.text(reopentext);
       }
-
-      form.find('.js-note-text').focus();
-      form
-        .find('.js-comment-resolve-button')
-        .attr('data-discussion-id', discussionID);
-    };
-
-    /*
-    Called when clicking on the "add a comment" button on the side of a diff line.
-
-    Inserts a temporary row for the form below the line.
-    Sets up the form and shows it.
-     */
-
-    Notes.prototype.onAddDiffNote = function(e) {
-      e.preventDefault();
-      const link = e.currentTarget || e.target;
-      const $link = $(link);
-      const showReplyInput = !$link.hasClass('js-diff-comment-avatar');
-      this.toggleDiffNote({
-        target: $link,
-        lineType: link.dataset.lineType,
-        showReplyInput
-      });
-    };
-
-    Notes.prototype.toggleDiffNote = function({
-      target,
-      lineType,
-      forceShow,
-      showReplyInput = false,
-    }) {
-      var $link, addForm, hasNotes, newForm, noteForm, replyButton, row, rowCssToAdd, targetContent, isDiffCommentAvatar;
-      $link = $(target);
-      row = $link.closest('tr');
-      const nextRow = row.next();
-      let targetRow = row;
-      if (nextRow.is('.notes_holder')) {
-        targetRow = nextRow;
+      if (closebtn.text() !== closetext) {
+        closebtn.text(closetext);
       }
-
-      hasNotes = nextRow.is('.notes_holder');
-      addForm = false;
-      let lineTypeSelector = '';
-      rowCssToAdd = '<tr class="notes_holder js-temp-notes-holder"><td class="notes_line" colspan="2"></td><td class="notes_content"><div class="content"></div></td></tr>';
-      // In parallel view, look inside the correct left/right pane
-      if (this.isParallelView()) {
-        lineTypeSelector = `.${lineType}`;
-        rowCssToAdd = '<tr class="notes_holder js-temp-notes-holder"><td class="notes_line old"></td><td class="notes_content parallel old"><div class="content"></div></td><td class="notes_line new"></td><td class="notes_content parallel new"><div class="content"></div></td></tr>';
+      if (reopenbtn.is(':not(.btn-comment-and-reopen)')) {
+        reopenbtn.addClass('btn-comment-and-reopen');
       }
-      const notesContentSelector = `.notes_content${lineTypeSelector} .content`;
-      let notesContent = targetRow.find(notesContentSelector);
-
-      if (hasNotes && showReplyInput) {
-        targetRow.show();
-        notesContent = targetRow.find(notesContentSelector);
-        if (notesContent.length) {
-          notesContent.show();
-          replyButton = notesContent.find('.js-discussion-reply-button:visible');
-          if (replyButton.length) {
-            this.replyToDiscussionNote(replyButton[0]);
-          } else {
-            // In parallel view, the form may not be present in one of the panes
-            noteForm = notesContent.find('.js-discussion-note-form');
-            if (noteForm.length === 0) {
-              addForm = true;
-            }
-          }
-        }
-      } else if (showReplyInput) {
-        // add a notes row and insert the form
-        row.after(rowCssToAdd);
-        targetRow = row.next();
-        notesContent = targetRow.find(notesContentSelector);
-        addForm = true;
-      } else {
-        const isCurrentlyShown = targetRow.find('.content:not(:empty)').is(':visible');
-        const isForced = forceShow === true || forceShow === false;
-        const showNow = forceShow === true || (!isCurrentlyShown && !isForced);
-
-        targetRow.toggle(showNow);
-        notesContent.toggle(showNow);
+      if (closebtn.is(':not(.btn-comment-and-close)')) {
+        closebtn.addClass('btn-comment-and-close');
       }
-
-      if (addForm) {
-        newForm = this.cleanForm(this.formClone.clone());
-        newForm.appendTo(notesContent);
-        // show the form
-        return this.setupDiscussionNoteForm($link, newForm);
+      if (discardbtn.is(':hidden')) {
+        return discardbtn.show();
       }
-    };
-
-    /*
-    Called in response to "cancel" on a diff note form.
-
-    Shows the reply button again.
-    Removes the form and if necessary it's temporary row.
-     */
-
-    Notes.prototype.removeDiscussionNoteForm = function(form) {
-      var glForm, row;
-      row = form.closest('tr');
-      glForm = form.data('gl-form');
-      glForm.destroy();
-      form.find('.js-note-text').data('autosave').reset();
-      // show the reply button (will only work for replies)
-      form
-        .prev('.discussion-reply-holder')
-        .show();
-      if (row.is('.js-temp-notes-holder')) {
-        // remove temporary row for diff lines
-        return row.remove();
-      } else {
-        // only remove the form
-        return form.remove();
+    } else {
+      reopentext = reopenbtn.data('original-text');
+      closetext = closebtn.data('original-text');
+      if (reopenbtn.text() !== reopentext) {
+        reopenbtn.text(reopentext);
       }
-    };
-
-    Notes.prototype.cancelDiscussionForm = function(e) {
-      var form;
-      e.preventDefault();
-      form = $(e.target).closest('.js-discussion-note-form');
-      return this.removeDiscussionNoteForm(form);
-    };
-
-    /*
-    Called after an attachment file has been selected.
-
-    Updates the file name for the selected attachment.
-     */
-
-    Notes.prototype.updateFormAttachment = function() {
-      var filename, form;
-      form = $(this).closest('form');
-      // get only the basename
-      filename = $(this).val().replace(/^.*[\\\/]/, '');
-      return form.find('.js-attachment-filename').text(filename);
-    };
-
-    /*
-    Called when the tab visibility changes
-     */
-
-    Notes.prototype.visibilityChange = function() {
-      return this.refresh();
-    };
-
-    Notes.prototype.updateTargetButtons = function(e) {
-      var closebtn, closetext, discardbtn, form, reopenbtn, reopentext, textarea;
-      textarea = $(e.target);
-      form = textarea.parents('form');
-      reopenbtn = form.find('.js-note-target-reopen');
-      closebtn = form.find('.js-note-target-close');
-      discardbtn = form.find('.js-note-discard');
-
-      if (textarea.val().trim().length > 0) {
-        reopentext = reopenbtn.attr('data-alternative-text');
-        closetext = closebtn.attr('data-alternative-text');
-        if (reopenbtn.text() !== reopentext) {
-          reopenbtn.text(reopentext);
-        }
-        if (closebtn.text() !== closetext) {
-          closebtn.text(closetext);
-        }
-        if (reopenbtn.is(':not(.btn-comment-and-reopen)')) {
-          reopenbtn.addClass('btn-comment-and-reopen');
-        }
-        if (closebtn.is(':not(.btn-comment-and-close)')) {
-          closebtn.addClass('btn-comment-and-close');
-        }
-        if (discardbtn.is(':hidden')) {
-          return discardbtn.show();
-        }
-      } else {
-        reopentext = reopenbtn.data('original-text');
-        closetext = closebtn.data('original-text');
-        if (reopenbtn.text() !== reopentext) {
-          reopenbtn.text(reopentext);
-        }
-        if (closebtn.text() !== closetext) {
-          closebtn.text(closetext);
-        }
-        if (reopenbtn.is('.btn-comment-and-reopen')) {
-          reopenbtn.removeClass('btn-comment-and-reopen');
-        }
-        if (closebtn.is('.btn-comment-and-close')) {
-          closebtn.removeClass('btn-comment-and-close');
-        }
-        if (discardbtn.is(':visible')) {
-          return discardbtn.hide();
-        }
+      if (closebtn.text() !== closetext) {
+        closebtn.text(closetext);
       }
-    };
-
-    Notes.prototype.putEditFormInPlace = function($el) {
-      var $editForm = $(this.getEditFormSelector($el));
-      var $note = $el.closest('.note');
-
-      $editForm.insertAfter($note.find('.note-text'));
-
-      var $originalContentEl = $note.find('.original-note-content');
-      var originalContent = $originalContentEl.text().trim();
-      var postUrl = $originalContentEl.data('post-url');
-      var targetId = $originalContentEl.data('target-id');
-      var targetType = $originalContentEl.data('target-type');
-
-      new gl.GLForm($editForm.find('form'), this.enableGFM);
-
-      $editForm.find('form')
-        .attr('action', postUrl)
-        .attr('data-remote', 'true');
-      $editForm.find('.js-form-target-id').val(targetId);
-      $editForm.find('.js-form-target-type').val(targetType);
-      $editForm.find('.js-note-text').focus().val(originalContent);
-      $editForm.find('.js-md-write-button').trigger('click');
-      $editForm.find('.referenced-users').hide();
-    };
-
-    Notes.prototype.putConflictEditWarningInPlace = function(noteEntity, $note) {
-      if ($note.find('.js-conflict-edit-warning').length === 0) {
-        const $alert = $(`<div class="js-conflict-edit-warning alert alert-danger">
-          This comment has changed since you started editing, please review the
-          <a href="#note_${noteEntity.id}" target="_blank" rel="noopener noreferrer">
-            updated comment
-          </a>
-          to ensure information is not lost
-        </div>`);
-        $alert.insertAfter($note.find('.note-text'));
+      if (reopenbtn.is('.btn-comment-and-reopen')) {
+        reopenbtn.removeClass('btn-comment-and-reopen');
       }
-    };
-
-    Notes.prototype.updateNotesCount = function(updateCount) {
-      return this.notesCountBadge.text(parseInt(this.notesCountBadge.text(), 10) + updateCount);
-    };
-
-    Notes.prototype.toggleCommitList = function(e) {
-      const $element = $(e.currentTarget);
-      const $closestSystemCommitList = $element.siblings('.system-note-commit-list');
-
-      $element.find('.fa').toggleClass('fa-angle-down').toggleClass('fa-angle-up');
-      $closestSystemCommitList.toggleClass('hide-shade');
-    };
-
-    /**
-    Scans system notes with `ul` elements in system note body
-    then collapse long commit list pushed by user to make it less
-    intrusive.
-     */
-    Notes.prototype.collapseLongCommitList = function() {
-      const systemNotes = $('#notes-list').find('li.system-note').has('ul');
-
-      $.each(systemNotes, function(index, systemNote) {
-        const $systemNote = $(systemNote);
-        const headerMessage = $systemNote.find('.note-text').find('p:first').text().replace(':', '');
-
-        $systemNote.find('.note-header .system-note-message').html(headerMessage);
-
-        if ($systemNote.find('li').length > MAX_VISIBLE_COMMIT_LIST_COUNT) {
-          $systemNote.find('.note-text').addClass('system-note-commit-list');
-          $systemNote.find('.system-note-commit-list-toggler').show();
-        } else {
-          $systemNote.find('.note-text').addClass('system-note-commit-list hide-shade');
-        }
-      });
-    };
-
-    Notes.prototype.addFlash = function(...flashParams) {
-      this.flashInstance = new Flash(...flashParams);
-    };
-
-    Notes.prototype.clearFlash = function() {
-      if (this.flashInstance && this.flashInstance.flashContainer) {
-        this.flashInstance.flashContainer.hide();
-        this.flashInstance = null;
+      if (closebtn.is('.btn-comment-and-close')) {
+        closebtn.removeClass('btn-comment-and-close');
       }
-    };
-
-    Notes.prototype.cleanForm = function($form) {
-      // Remove JS classes that are not needed here
-      $form
-        .find('.js-comment-type-dropdown')
-        .removeClass('btn-group');
-
-      // Remove dropdown
-      $form
-        .find('.dropdown-menu')
-        .remove();
-
-      return $form;
-    };
-
-    /**
-     * Check if note does not exists on page
-     */
-    Notes.isNewNote = function(noteEntity, noteIds) {
-      return $.inArray(noteEntity.id, noteIds) === -1;
-    };
-
-    /**
-     * Check if $note already contains the `noteEntity` content
-     */
-    Notes.isUpdatedNote = function(noteEntity, $note) {
-      // There can be CRLF vs LF mismatches if we don't sanitize and compare the same way
-      const sanitizedNoteEntityText = normalizeNewlines(noteEntity.note.trim());
-      const currentNoteText = normalizeNewlines(
-        $note.find('.original-note-content').first().text().trim()
-      );
-      return sanitizedNoteEntityText !== currentNoteText;
-    };
-
-    Notes.checkMergeRequestStatus = function() {
-      if (gl.utils.getPagePath(1) === 'merge_requests') {
-        gl.mrWidget.checkStatus();
+      if (discardbtn.is(':visible')) {
+        return discardbtn.hide();
       }
-    };
+    }
+  }
+
+  putEditFormInPlace($el) {
+    var $editForm = $(this.getEditFormSelector($el));
+    var $note = $el.closest('.note');
+
+    $editForm.insertAfter($note.find('.note-text'));
+
+    var $originalContentEl = $note.find('.original-note-content');
+    var originalContent = $originalContentEl.text().trim();
+    var postUrl = $originalContentEl.data('post-url');
+    var targetId = $originalContentEl.data('target-id');
+    var targetType = $originalContentEl.data('target-type');
+
+    new gl.GLForm($editForm.find('form'), this.enableGFM);
+
+    $editForm.find('form')
+      .attr('action', postUrl)
+      .attr('data-remote', 'true');
+    $editForm.find('.js-form-target-id').val(targetId);
+    $editForm.find('.js-form-target-type').val(targetType);
+    $editForm.find('.js-note-text').focus().val(originalContent);
+    $editForm.find('.js-md-write-button').trigger('click');
+    $editForm.find('.referenced-users').hide();
+  }
+
+  putConflictEditWarningInPlace(noteEntity, $note) {
+    if ($note.find('.js-conflict-edit-warning').length === 0) {
+      const $alert = $(`<div class="js-conflict-edit-warning alert alert-danger">
+        This comment has changed since you started editing, please review the
+        <a href="#note_${noteEntity.id}" target="_blank" rel="noopener noreferrer">
+          updated comment
+        </a>
+        to ensure information is not lost
+      </div>`);
+      $alert.insertAfter($note.find('.note-text'));
+    }
+  }
 
-    Notes.animateAppendNote = function(noteHtml, $notesList) {
-      const $note = $(noteHtml);
+  updateNotesCount(updateCount) {
+    return this.notesCountBadge.text(parseInt(this.notesCountBadge.text(), 10) + updateCount);
+  }
 
-      $note.addClass('fade-in-full').renderGFM();
-      $notesList.append($note);
-      return $note;
-    };
+  toggleCommitList(e) {
+    const $element = $(e.currentTarget);
+    const $closestSystemCommitList = $element.siblings('.system-note-commit-list');
 
-    Notes.animateUpdateNote = function(noteHtml, $note) {
-      const $updatedNote = $(noteHtml);
+    $element.find('.fa').toggleClass('fa-angle-down').toggleClass('fa-angle-up');
+    $closestSystemCommitList.toggleClass('hide-shade');
+  }
 
-      $updatedNote.addClass('fade-in').renderGFM();
-      $note.replaceWith($updatedNote);
-      return $updatedNote;
-    };
+  /**
+   * Scans system notes with `ul` elements in system note body
+   * then collapse long commit list pushed by user to make it less
+   * intrusive.
+   */
+  collapseLongCommitList() {
+    const systemNotes = $('#notes-list').find('li.system-note').has('ul');
 
-    /**
-     * Get data from Form attributes to use for saving/submitting comment.
-     */
-    Notes.prototype.getFormData = function($form) {
-      return {
-        formData: $form.serialize(),
-        formContent: _.escape($form.find('.js-note-text').val()),
-        formAction: $form.attr('action'),
-      };
-    };
+    $.each(systemNotes, function(index, systemNote) {
+      const $systemNote = $(systemNote);
+      const headerMessage = $systemNote.find('.note-text').find('p:first').text().replace(':', '');
 
-    /**
-     * Identify if comment has any slash commands
-     */
-    Notes.prototype.hasSlashCommands = function(formContent) {
-      return REGEX_SLASH_COMMANDS.test(formContent);
-    };
-
-    /**
-     * Remove slash commands and leave comment with pure message
-     */
-    Notes.prototype.stripSlashCommands = function(formContent) {
-      return formContent.replace(REGEX_SLASH_COMMANDS, '').trim();
-    };
+      $systemNote.find('.note-header .system-note-message').html(headerMessage);
 
-    /**
-     * Gets appropriate description from slash commands found in provided `formContent`
-     */
-    Notes.prototype.getSlashCommandDescription = function (formContent, availableSlashCommands = []) {
-      let tempFormContent;
+      if ($systemNote.find('li').length > MAX_VISIBLE_COMMIT_LIST_COUNT) {
+        $systemNote.find('.note-text').addClass('system-note-commit-list');
+        $systemNote.find('.system-note-commit-list-toggler').show();
+      } else {
+        $systemNote.find('.note-text').addClass('system-note-commit-list hide-shade');
+      }
+    });
+  }
 
-      // Identify executed slash commands from `formContent`
-      const executedCommands = availableSlashCommands.filter((command, index) => {
-        const commandRegex = new RegExp(`/${command.name}`);
-        return commandRegex.test(formContent);
-      });
+  addFlash(...flashParams) {
+    this.flashInstance = new Flash(...flashParams);
+  }
 
-      if (executedCommands && executedCommands.length) {
-        if (executedCommands.length > 1) {
-          tempFormContent = 'Applying multiple commands';
-        } else {
-          const commandDescription = executedCommands[0].description.toLowerCase();
-          tempFormContent = `Applying command to ${commandDescription}`;
-        }
+  clearFlash() {
+    if (this.flashInstance && this.flashInstance.flashContainer) {
+      this.flashInstance.flashContainer.hide();
+      this.flashInstance = null;
+    }
+  }
+
+  cleanForm($form) {
+    // Remove JS classes that are not needed here
+    $form
+      .find('.js-comment-type-dropdown')
+      .removeClass('btn-group');
+
+    // Remove dropdown
+    $form
+      .find('.dropdown-menu')
+      .remove();
+
+    return $form;
+  }
+
+  /**
+   * Check if note does not exists on page
+   */
+  static isNewNote(noteEntity, noteIds) {
+    return $.inArray(noteEntity.id, noteIds) === -1;
+  }
+
+  /**
+   * Check if $note already contains the `noteEntity` content
+   */
+  static isUpdatedNote(noteEntity, $note) {
+    // There can be CRLF vs LF mismatches if we don't sanitize and compare the same way
+    const sanitizedNoteEntityText = normalizeNewlines(noteEntity.note.trim());
+    const currentNoteText = normalizeNewlines(
+      $note.find('.original-note-content').first().text().trim()
+    );
+    return sanitizedNoteEntityText !== currentNoteText;
+  }
+
+  static checkMergeRequestStatus() {
+    if (gl.utils.getPagePath(1) === 'merge_requests') {
+      gl.mrWidget.checkStatus();
+    }
+  }
+
+  static animateAppendNote(noteHtml, $notesList) {
+    const $note = $(noteHtml);
+
+    $note.addClass('fade-in-full').renderGFM();
+    $notesList.append($note);
+    return $note;
+  }
+
+  static animateUpdateNote(noteHtml, $note) {
+    const $updatedNote = $(noteHtml);
+
+    $updatedNote.addClass('fade-in').renderGFM();
+    $note.replaceWith($updatedNote);
+    return $updatedNote;
+  }
+
+  /**
+   * Get data from Form attributes to use for saving/submitting comment.
+   */
+  getFormData($form) {
+    return {
+      formData: $form.serialize(),
+      formContent: _.escape($form.find('.js-note-text').val()),
+      formAction: $form.attr('action'),
+    };
+  }
+
+  /**
+   * Identify if comment has any quick actions
+   */
+  hasQuickActions(formContent) {
+    return REGEX_QUICK_ACTIONS.test(formContent);
+  }
+
+  /**
+   * Remove quick actions and leave comment with pure message
+   */
+  stripQuickActions(formContent) {
+    return formContent.replace(REGEX_QUICK_ACTIONS, '').trim();
+  }
+
+  /**
+   * Gets appropriate description from quick actions found in provided `formContent`
+   */
+  getQuickActionDescription(formContent, availableQuickActions = []) {
+    let tempFormContent;
+
+    // Identify executed quick actions from `formContent`
+    const executedCommands = availableQuickActions.filter((command, index) => {
+      const commandRegex = new RegExp(`/${command.name}`);
+      return commandRegex.test(formContent);
+    });
+
+    if (executedCommands && executedCommands.length) {
+      if (executedCommands.length > 1) {
+        tempFormContent = 'Applying multiple commands';
       } else {
-        tempFormContent = 'Applying command';
+        const commandDescription = executedCommands[0].description.toLowerCase();
+        tempFormContent = `Applying command to ${commandDescription}`;
       }
+    } else {
+      tempFormContent = 'Applying command';
+    }
 
-      return tempFormContent;
-    };
-
-    /**
-     * Create placeholder note DOM element populated with comment body
-     * that we will show while comment is being posted.
-     * Once comment is _actually_ posted on server, we will have final element
-     * in response that we will show in place of this temporary element.
-     */
-    Notes.prototype.createPlaceholderNote = function ({ formContent, uniqueId, isDiscussionNote, currentUsername, currentUserFullname, currentUserAvatar }) {
-      const discussionClass = isDiscussionNote ? 'discussion' : '';
-      const $tempNote = $(
-        `<li id="${uniqueId}" class="note being-posted fade-in-half timeline-entry">
-           <div class="timeline-entry-inner">
-              <div class="timeline-icon">
-                 <a href="/${currentUsername}">
-                   <img class="avatar s40" src="${currentUserAvatar}">
-                 </a>
-              </div>
-              <div class="timeline-content ${discussionClass}">
-                 <div class="note-header">
-                    <div class="note-header-info">
-                       <a href="/${currentUsername}">
-                         <span class="hidden-xs">${currentUserFullname}</span>
-                         <span class="note-headline-light">@${currentUsername}</span>
-                       </a>
-                    </div>
+    return tempFormContent;
+  }
+
+  /**
+   * Create placeholder note DOM element populated with comment body
+   * that we will show while comment is being posted.
+   * Once comment is _actually_ posted on server, we will have final element
+   * in response that we will show in place of this temporary element.
+   */
+  createPlaceholderNote({ formContent, uniqueId, isDiscussionNote, currentUsername, currentUserFullname, currentUserAvatar }) {
+    const discussionClass = isDiscussionNote ? 'discussion' : '';
+    const $tempNote = $(
+      `<li id="${uniqueId}" class="note being-posted fade-in-half timeline-entry">
+         <div class="timeline-entry-inner">
+            <div class="timeline-icon">
+               <a href="/${currentUsername}">
+                 <img class="avatar s40" src="${currentUserAvatar}">
+               </a>
+            </div>
+            <div class="timeline-content ${discussionClass}">
+               <div class="note-header">
+                  <div class="note-header-info">
+                     <a href="/${currentUsername}">
+                       <span class="hidden-xs">${currentUserFullname}</span>
+                       <span class="note-headline-light">@${currentUsername}</span>
+                     </a>
+                  </div>
+               </div>
+               <div class="note-body">
+                 <div class="note-text">
+                   <p>${formContent}</p>
                  </div>
-                 <div class="note-body">
-                   <div class="note-text">
-                     <p>${formContent}</p>
-                   </div>
-                 </div>
-              </div>
-           </div>
-        </li>`
-      );
-
-      return $tempNote;
-    };
-
-    /**
-     * Create Placeholder System Note DOM element populated with slash command description
-     */
-    Notes.prototype.createPlaceholderSystemNote = function ({ formContent, uniqueId }) {
-      const $tempNote = $(
-        `<li id="${uniqueId}" class="note system-note timeline-entry being-posted fade-in-half">
-           <div class="timeline-entry-inner">
-             <div class="timeline-content">
-               <i>${formContent}</i>
-             </div>
+               </div>
+            </div>
+         </div>
+      </li>`
+    );
+
+    return $tempNote;
+  }
+
+  /**
+   * Create Placeholder System Note DOM element populated with quick action description
+   */
+  createPlaceholderSystemNote({ formContent, uniqueId }) {
+    const $tempNote = $(
+      `<li id="${uniqueId}" class="note system-note timeline-entry being-posted fade-in-half">
+         <div class="timeline-entry-inner">
+           <div class="timeline-content">
+             <i>${formContent}</i>
            </div>
-         </li>`
-      );
+         </div>
+       </li>`
+    );
+
+    return $tempNote;
+  }
+
+  /**
+   * This method does following tasks step-by-step whenever a new comment
+   * is submitted by user (both main thread comments as well as discussion comments).
+   *
+   * 1) Get Form metadata
+   * 2) Identify comment type; a) Main thread b) Discussion thread c) Discussion resolve
+   * 3) Build temporary placeholder element (using `createPlaceholderNote`)
+   * 4) Show placeholder note on UI
+   * 5) Perform network request to submit the note using `gl.utils.ajaxPost`
+   *    a) If request is successfully completed
+   *        1. Remove placeholder element
+   *        2. Show submitted Note element
+   *        3. Perform post-submit errands
+   *           a. Mark discussion as resolved if comment submission was for resolve.
+   *           b. Reset comment form to original state.
+   *    b) If request failed
+   *        1. Remove placeholder element
+   *        2. Show error Flash message about failure
+   */
+  postComment(e) {
+    e.preventDefault();
+
+    // Get Form metadata
+    const $submitBtn = $(e.target);
+    let $form = $submitBtn.parents('form');
+    const $closeBtn = $form.find('.js-note-target-close');
+    const isDiscussionNote = $submitBtn.parent().find('li.droplab-item-selected').attr('id') === 'discussion';
+    const isMainForm = $form.hasClass('js-main-target-form');
+    const isDiscussionForm = $form.hasClass('js-discussion-note-form');
+    const isDiscussionResolve = $submitBtn.hasClass('js-comment-resolve-button');
+    const { formData, formContent, formAction } = this.getFormData($form);
+    let noteUniqueId;
+    let systemNoteUniqueId;
+    let hasQuickActions = false;
+    let $notesContainer;
+    let tempFormContent;
+
+    // Get reference to notes container based on type of comment
+    if (isDiscussionForm) {
+      $notesContainer = $form.parent('.discussion-notes').find('.notes');
+    } else if (isMainForm) {
+      $notesContainer = $('ul.main-notes-list');
+    }
 
-      return $tempNote;
-    };
+    // If comment is to resolve discussion, disable submit buttons while
+    // comment posting is finished.
+    if (isDiscussionResolve) {
+      $submitBtn.disable();
+      $form.find('.js-comment-submit-button').disable();
+    }
 
-    /**
-     * This method does following tasks step-by-step whenever a new comment
-     * is submitted by user (both main thread comments as well as discussion comments).
-     *
-     * 1) Get Form metadata
-     * 2) Identify comment type; a) Main thread b) Discussion thread c) Discussion resolve
-     * 3) Build temporary placeholder element (using `createPlaceholderNote`)
-     * 4) Show placeholder note on UI
-     * 5) Perform network request to submit the note using `gl.utils.ajaxPost`
-     *    a) If request is successfully completed
-     *        1. Remove placeholder element
-     *        2. Show submitted Note element
-     *        3. Perform post-submit errands
-     *           a. Mark discussion as resolved if comment submission was for resolve.
-     *           b. Reset comment form to original state.
-     *    b) If request failed
-     *        1. Remove placeholder element
-     *        2. Show error Flash message about failure
-     */
-    Notes.prototype.postComment = function(e) {
-      e.preventDefault();
-
-      // Get Form metadata
-      const $submitBtn = $(e.target);
-      let $form = $submitBtn.parents('form');
-      const $closeBtn = $form.find('.js-note-target-close');
-      const isDiscussionNote = $submitBtn.parent().find('li.droplab-item-selected').attr('id') === 'discussion';
-      const isMainForm = $form.hasClass('js-main-target-form');
-      const isDiscussionForm = $form.hasClass('js-discussion-note-form');
-      const isDiscussionResolve = $submitBtn.hasClass('js-comment-resolve-button');
-      const { formData, formContent, formAction } = this.getFormData($form);
-      let noteUniqueId;
-      let systemNoteUniqueId;
-      let hasSlashCommands = false;
-      let $notesContainer;
-      let tempFormContent;
-
-      // Get reference to notes container based on type of comment
-      if (isDiscussionForm) {
-        $notesContainer = $form.parent('.discussion-notes').find('.notes');
-      } else if (isMainForm) {
-        $notesContainer = $('ul.main-notes-list');
-      }
+    tempFormContent = formContent;
+    if (this.hasQuickActions(formContent)) {
+      tempFormContent = this.stripQuickActions(formContent);
+      hasQuickActions = true;
+    }
 
-      // If comment is to resolve discussion, disable submit buttons while
-      // comment posting is finished.
-      if (isDiscussionResolve) {
-        $submitBtn.disable();
-        $form.find('.js-comment-submit-button').disable();
-      }
+    // Show placeholder note
+    if (tempFormContent) {
+      noteUniqueId = _.uniqueId('tempNote_');
+      $notesContainer.append(this.createPlaceholderNote({
+        formContent: tempFormContent,
+        uniqueId: noteUniqueId,
+        isDiscussionNote,
+        currentUsername: gon.current_username,
+        currentUserFullname: gon.current_user_fullname,
+        currentUserAvatar: gon.current_user_avatar_url,
+      }));
+    }
 
-      tempFormContent = formContent;
-      if (this.hasSlashCommands(formContent)) {
-        tempFormContent = this.stripSlashCommands(formContent);
-        hasSlashCommands = true;
-      }
+    // Show placeholder system note
+    if (hasQuickActions) {
+      systemNoteUniqueId = _.uniqueId('tempSystemNote_');
+      $notesContainer.append(this.createPlaceholderSystemNote({
+        formContent: this.getQuickActionDescription(formContent, AjaxCache.get(gl.GfmAutoComplete.dataSources.commands)),
+        uniqueId: systemNoteUniqueId,
+      }));
+    }
 
-      // Show placeholder note
-      if (tempFormContent) {
-        noteUniqueId = _.uniqueId('tempNote_');
-        $notesContainer.append(this.createPlaceholderNote({
-          formContent: tempFormContent,
-          uniqueId: noteUniqueId,
-          isDiscussionNote,
-          currentUsername: gon.current_username,
-          currentUserFullname: gon.current_user_fullname,
-          currentUserAvatar: gon.current_user_avatar_url,
-        }));
+    // Clear the form textarea
+    if ($notesContainer.length) {
+      if (isMainForm) {
+        this.resetMainTargetForm(e);
+      } else if (isDiscussionForm) {
+        this.removeDiscussionNoteForm($form);
       }
+    }
 
-      // Show placeholder system note
-      if (hasSlashCommands) {
-        systemNoteUniqueId = _.uniqueId('tempSystemNote_');
-        $notesContainer.append(this.createPlaceholderSystemNote({
-          formContent: this.getSlashCommandDescription(formContent, AjaxCache.get(gl.GfmAutoComplete.dataSources.commands)),
-          uniqueId: systemNoteUniqueId,
-        }));
-      }
+    /* eslint-disable promise/catch-or-return */
+    // Make request to submit comment on server
+    gl.utils.ajaxPost(formAction, formData)
+      .then((note) => {
+        // Submission successful! remove placeholder
+        $notesContainer.find(`#${noteUniqueId}`).remove();
 
-      // Clear the form textarea
-      if ($notesContainer.length) {
-        if (isMainForm) {
-          this.resetMainTargetForm(e);
-        } else if (isDiscussionForm) {
-          this.removeDiscussionNoteForm($form);
+        // Reset cached commands list when command is applied
+        if (hasQuickActions) {
+          $form.find('textarea.js-note-text').trigger('clear-commands-cache.atwho');
         }
-      }
-
-      /* eslint-disable promise/catch-or-return */
-      // Make request to submit comment on server
-      gl.utils.ajaxPost(formAction, formData)
-        .then((note) => {
-          // Submission successful! remove placeholder
-          $notesContainer.find(`#${noteUniqueId}`).remove();
 
-          // Reset cached commands list when command is applied
-          if (hasSlashCommands) {
-            $form.find('textarea.js-note-text').trigger('clear-commands-cache.atwho');
-          }
-
-          // Clear previous form errors
-          this.clearFlashWrapper();
-
-          // Check if this was discussion comment
-          if (isDiscussionForm) {
-            // Remove flash-container
-            $notesContainer.find('.flash-container').remove();
+        // Clear previous form errors
+        this.clearFlashWrapper();
 
-            // If comment intends to resolve discussion, do the same.
-            if (isDiscussionResolve) {
-              $form
-                .attr('data-discussion-id', $submitBtn.data('discussion-id'))
-                .attr('data-resolve-all', 'true')
-                .attr('data-project-path', $submitBtn.data('project-path'));
-            }
-
-            // Show final note element on UI
-            this.addDiscussionNote($form, note, $notesContainer.length === 0);
+        // Check if this was discussion comment
+        if (isDiscussionForm) {
+          // Remove flash-container
+          $notesContainer.find('.flash-container').remove();
 
-            // append flash-container to the Notes list
-            if ($notesContainer.length) {
-              $notesContainer.append('<div class="flash-container" style="display: none;"></div>');
-            }
-          } else if (isMainForm) { // Check if this was main thread comment
-            // Show final note element on UI and perform form and action buttons cleanup
-            this.addNote($form, note);
-            this.reenableTargetFormSubmitButton(e);
+          // If comment intends to resolve discussion, do the same.
+          if (isDiscussionResolve) {
+            $form
+              .attr('data-discussion-id', $submitBtn.data('discussion-id'))
+              .attr('data-resolve-all', 'true')
+              .attr('data-project-path', $submitBtn.data('project-path'));
           }
 
-          if (note.commands_changes) {
-            this.handleSlashCommands(note);
+          // Show final note element on UI
+          this.addDiscussionNote($form, note, $notesContainer.length === 0);
+
+          // append flash-container to the Notes list
+          if ($notesContainer.length) {
+            $notesContainer.append('<div class="flash-container" style="display: none;"></div>');
           }
+        } else if (isMainForm) { // Check if this was main thread comment
+          // Show final note element on UI and perform form and action buttons cleanup
+          this.addNote($form, note);
+          this.reenableTargetFormSubmitButton(e);
+        }
 
-          $form.trigger('ajax:success', [note]);
-        }).fail(() => {
-          // Submission failed, remove placeholder note and show Flash error message
-          $notesContainer.find(`#${noteUniqueId}`).remove();
+        if (note.commands_changes) {
+          this.handleQuickActions(note);
+        }
 
-          if (hasSlashCommands) {
-            $notesContainer.find(`#${systemNoteUniqueId}`).remove();
-          }
+        $form.trigger('ajax:success', [note]);
+      }).fail(() => {
+        // Submission failed, remove placeholder note and show Flash error message
+        $notesContainer.find(`#${noteUniqueId}`).remove();
 
-          // Show form again on UI on failure
-          if (isDiscussionForm && $notesContainer.length) {
-            const replyButton = $notesContainer.parent().find('.js-discussion-reply-button');
-            this.replyToDiscussionNote(replyButton[0]);
-            $form = $notesContainer.parent().find('form');
-          }
+        if (hasQuickActions) {
+          $notesContainer.find(`#${systemNoteUniqueId}`).remove();
+        }
 
-          $form.find('.js-note-text').val(formContent);
-          this.reenableTargetFormSubmitButton(e);
-          this.addNoteError($form);
-        });
+        // Show form again on UI on failure
+        if (isDiscussionForm && $notesContainer.length) {
+          const replyButton = $notesContainer.parent().find('.js-discussion-reply-button');
+          this.replyToDiscussionNote(replyButton[0]);
+          $form = $notesContainer.parent().find('form');
+        }
 
-      return $closeBtn.text($closeBtn.data('original-text'));
-    };
+        $form.find('.js-note-text').val(formContent);
+        this.reenableTargetFormSubmitButton(e);
+        this.addNoteError($form);
+      });
 
-    /**
-     * This method does following tasks step-by-step whenever an existing comment
-     * is updated by user (both main thread comments as well as discussion comments).
-     *
-     * 1) Get Form metadata
-     * 2) Update note element with new content
-     * 3) Perform network request to submit the updated note using `gl.utils.ajaxPost`
-     *    a) If request is successfully completed
-     *        1. Show submitted Note element
-     *    b) If request failed
-     *        1. Revert Note element to original content
-     *        2. Show error Flash message about failure
-     */
-    Notes.prototype.updateComment = function(e) {
-      e.preventDefault();
-
-      // Get Form metadata
-      const $submitBtn = $(e.target);
-      const $form = $submitBtn.parents('form');
-      const $closeBtn = $form.find('.js-note-target-close');
-      const $editingNote = $form.parents('.note.is-editing');
-      const $noteBody = $editingNote.find('.js-task-list-container');
-      const $noteBodyText = $noteBody.find('.note-text');
-      const { formData, formContent, formAction } = this.getFormData($form);
-
-      // Cache original comment content
-      const cachedNoteBodyText = $noteBodyText.html();
-
-      // Show updated comment content temporarily
-      $noteBodyText.html(_.escape(formContent));
-      $editingNote.removeClass('is-editing fade-in-full').addClass('being-posted fade-in-half');
-      $editingNote.find('.note-headline-meta a').html('<i class="fa fa-spinner fa-spin" aria-label="Comment is being updated" aria-hidden="true"></i>');
-
-      /* eslint-disable promise/catch-or-return */
-      // Make request to update comment on server
-      gl.utils.ajaxPost(formAction, formData)
-        .then((note) => {
-          // Submission successful! render final note element
-          this.updateNote(note, $editingNote);
-        })
-        .fail(() => {
-          // Submission failed, revert back to original note
-          $noteBodyText.html(_.escape(cachedNoteBodyText));
-          $editingNote.removeClass('being-posted fade-in');
-          $editingNote.find('.fa.fa-spinner').remove();
-
-          // Show Flash message about failure
-          this.updateNoteError();
-        });
+    return $closeBtn.text($closeBtn.data('original-text'));
+  }
+
+  /**
+   * This method does following tasks step-by-step whenever an existing comment
+   * is updated by user (both main thread comments as well as discussion comments).
+   *
+   * 1) Get Form metadata
+   * 2) Update note element with new content
+   * 3) Perform network request to submit the updated note using `gl.utils.ajaxPost`
+   *    a) If request is successfully completed
+   *        1. Show submitted Note element
+   *    b) If request failed
+   *        1. Revert Note element to original content
+   *        2. Show error Flash message about failure
+   */
+  updateComment(e) {
+    e.preventDefault();
+
+    // Get Form metadata
+    const $submitBtn = $(e.target);
+    const $form = $submitBtn.parents('form');
+    const $closeBtn = $form.find('.js-note-target-close');
+    const $editingNote = $form.parents('.note.is-editing');
+    const $noteBody = $editingNote.find('.js-task-list-container');
+    const $noteBodyText = $noteBody.find('.note-text');
+    const { formData, formContent, formAction } = this.getFormData($form);
+
+    // Cache original comment content
+    const cachedNoteBodyText = $noteBodyText.html();
+
+    // Show updated comment content temporarily
+    $noteBodyText.html(formContent);
+    $editingNote.removeClass('is-editing fade-in-full').addClass('being-posted fade-in-half');
+    $editingNote.find('.note-headline-meta a').html('<i class="fa fa-spinner fa-spin" aria-label="Comment is being updated" aria-hidden="true"></i>');
+
+    /* eslint-disable promise/catch-or-return */
+    // Make request to update comment on server
+    gl.utils.ajaxPost(formAction, formData)
+      .then((note) => {
+        // Submission successful! render final note element
+        this.updateNote(note, $editingNote);
+      })
+      .fail(() => {
+        // Submission failed, revert back to original note
+        $noteBodyText.html(_.escape(cachedNoteBodyText));
+        $editingNote.removeClass('being-posted fade-in');
+        $editingNote.find('.fa.fa-spinner').remove();
+
+        // Show Flash message about failure
+        this.updateNoteError();
+      });
 
-      return $closeBtn.text($closeBtn.data('original-text'));
-    };
+    return $closeBtn.text($closeBtn.data('original-text'));
+  }
+}
 
-    return Notes;
-  })();
-}).call(window);
+window.Notes = Notes;
diff --git a/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.js b/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.js
deleted file mode 100644
index 4d623763ca7f8f247220ce694660f57be13139ea..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.js
+++ /dev/null
@@ -1,145 +0,0 @@
-import Vue from 'vue';
-
-const inputNameAttribute = 'schedule[cron]';
-
-export default {
-  props: {
-    initialCronInterval: {
-      type: String,
-      required: false,
-      default: '',
-    },
-  },
-  data() {
-    return {
-      inputNameAttribute,
-      cronInterval: this.initialCronInterval,
-      cronIntervalPresets: {
-        everyDay: '0 4 * * *',
-        everyWeek: '0 4 * * 0',
-        everyMonth: '0 4 1 * *',
-      },
-      cronSyntaxUrl: 'https://en.wikipedia.org/wiki/Cron',
-      customInputEnabled: false,
-    };
-  },
-  computed: {
-    intervalIsPreset() {
-      return _.contains(this.cronIntervalPresets, this.cronInterval);
-    },
-    // The text input is editable when there's a custom interval, or when it's
-    // a preset interval and the user clicks the 'custom' radio button
-    isEditable() {
-      return !!(this.customInputEnabled || !this.intervalIsPreset);
-    },
-  },
-  methods: {
-    toggleCustomInput(shouldEnable) {
-      this.customInputEnabled = shouldEnable;
-
-      if (shouldEnable) {
-        // We need to change the value so other radios don't remain selected
-        // because the model (cronInterval) hasn't changed. The server trims it.
-        this.cronInterval = `${this.cronInterval} `;
-      }
-    },
-  },
-  created() {
-    if (this.intervalIsPreset) {
-      this.enableCustomInput = false;
-    }
-  },
-  watch: {
-    cronInterval() {
-      // updates field validation state when model changes, as
-      // glFieldError only updates on input.
-      Vue.nextTick(() => {
-        gl.pipelineScheduleFieldErrors.updateFormValidityState();
-      });
-    },
-  },
-  template: `
-    <div class="interval-pattern-form-group">
-      <div class="cron-preset-radio-input">
-        <input
-          id="custom"
-          class="label-light"
-          type="radio"
-          :name="inputNameAttribute"
-          :value="cronInterval"
-          :checked="isEditable"
-          @click="toggleCustomInput(true)"
-        />
-
-        <label for="custom">
-          Custom
-        </label>
-
-        <span class="cron-syntax-link-wrap">
-          (<a :href="cronSyntaxUrl" target="_blank">Cron syntax</a>)
-        </span>
-      </div>
-
-      <div class="cron-preset-radio-input">
-        <input
-          id="every-day"
-          class="label-light"
-          type="radio"
-          v-model="cronInterval"
-          :name="inputNameAttribute"
-          :value="cronIntervalPresets.everyDay"
-          @click="toggleCustomInput(false)"
-        />
-
-        <label class="label-light" for="every-day">
-          Every day (at 4:00am)
-        </label>
-      </div>
-
-      <div class="cron-preset-radio-input">
-        <input
-          id="every-week"
-          class="label-light"
-          type="radio"
-          v-model="cronInterval"
-          :name="inputNameAttribute"
-          :value="cronIntervalPresets.everyWeek"
-          @click="toggleCustomInput(false)"
-        />
-
-        <label class="label-light" for="every-week">
-          Every week (Sundays at 4:00am)
-        </label>
-      </div>
-
-      <div class="cron-preset-radio-input">
-        <input
-          id="every-month"
-          class="label-light"
-          type="radio"
-          v-model="cronInterval"
-          :name="inputNameAttribute"
-          :value="cronIntervalPresets.everyMonth"
-          @click="toggleCustomInput(false)"
-        />
-
-        <label class="label-light" for="every-month">
-          Every month (on the 1st at 4:00am)
-        </label>
-      </div>
-
-      <div class="cron-interval-input-wrapper">
-        <input
-          id="schedule_cron"
-          class="form-control inline cron-interval-input"
-          type="text"
-          placeholder="Define a custom pattern with cron syntax"
-          required="true"
-          v-model="cronInterval"
-          :name="inputNameAttribute"
-          :disabled="!isEditable"
-        />
-      </div>
-    </div>
-  `,
-};
diff --git a/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.vue b/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.vue
new file mode 100644
index 0000000000000000000000000000000000000000..ce46b3fa3fada130bdb1bd62dfe1103ac03dee3f
--- /dev/null
+++ b/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.vue
@@ -0,0 +1,144 @@
+<script>
+  export default {
+    props: {
+      initialCronInterval: {
+        type: String,
+        required: false,
+        default: '',
+      },
+    },
+    data() {
+      return {
+        inputNameAttribute: 'schedule[cron]',
+        cronInterval: this.initialCronInterval,
+        cronIntervalPresets: {
+          everyDay: '0 4 * * *',
+          everyWeek: '0 4 * * 0',
+          everyMonth: '0 4 1 * *',
+        },
+        cronSyntaxUrl: 'https://en.wikipedia.org/wiki/Cron',
+        customInputEnabled: false,
+      };
+    },
+    computed: {
+      intervalIsPreset() {
+        return _.contains(this.cronIntervalPresets, this.cronInterval);
+      },
+      // The text input is editable when there's a custom interval, or when it's
+      // a preset interval and the user clicks the 'custom' radio button
+      isEditable() {
+        return !!(this.customInputEnabled || !this.intervalIsPreset);
+      },
+    },
+    methods: {
+      toggleCustomInput(shouldEnable) {
+        this.customInputEnabled = shouldEnable;
+
+        if (shouldEnable) {
+          // We need to change the value so other radios don't remain selected
+          // because the model (cronInterval) hasn't changed. The server trims it.
+          this.cronInterval = `${this.cronInterval} `;
+        }
+      },
+    },
+    created() {
+      if (this.intervalIsPreset) {
+        this.enableCustomInput = false;
+      }
+    },
+    watch: {
+      cronInterval() {
+        // updates field validation state when model changes, as
+        // glFieldError only updates on input.
+        this.$nextTick(() => {
+          gl.pipelineScheduleFieldErrors.updateFormValidityState();
+        });
+      },
+    },
+  };
+</script>
+
+<template>
+  <div class="interval-pattern-form-group">
+    <div class="cron-preset-radio-input">
+      <input
+        id="custom"
+        class="label-light"
+        type="radio"
+        :name="inputNameAttribute"
+        :value="cronInterval"
+        :checked="isEditable"
+        @click="toggleCustomInput(true)"
+      />
+
+      <label for="custom">
+        {{ s__('PipelineSheduleIntervalPattern|Custom') }}
+      </label>
+
+      <span class="cron-syntax-link-wrap">
+        (<a :href="cronSyntaxUrl" target="_blank">{{ __('Cron syntax') }}</a>)
+      </span>
+    </div>
+
+    <div class="cron-preset-radio-input">
+      <input
+        id="every-day"
+        class="label-light"
+        type="radio"
+        v-model="cronInterval"
+        :name="inputNameAttribute"
+        :value="cronIntervalPresets.everyDay"
+        @click="toggleCustomInput(false)"
+      />
+
+      <label class="label-light" for="every-day">
+        {{ __('Every day (at 4:00am)') }}
+      </label>
+    </div>
+
+    <div class="cron-preset-radio-input">
+      <input
+        id="every-week"
+        class="label-light"
+        type="radio"
+        v-model="cronInterval"
+        :name="inputNameAttribute"
+        :value="cronIntervalPresets.everyWeek"
+        @click="toggleCustomInput(false)"
+      />
+
+      <label class="label-light" for="every-week">
+        {{ __('Every week (Sundays at 4:00am)') }}
+      </label>
+    </div>
+
+    <div class="cron-preset-radio-input">
+      <input
+        id="every-month"
+        class="label-light"
+        type="radio"
+        v-model="cronInterval"
+        :name="inputNameAttribute"
+        :value="cronIntervalPresets.everyMonth"
+        @click="toggleCustomInput(false)"
+      />
+
+      <label class="label-light" for="every-month">
+        {{ __('Every month (on the 1st at 4:00am)') }}
+      </label>
+    </div>
+
+    <div class="cron-interval-input-wrapper">
+      <input
+        id="schedule_cron"
+        class="form-control inline cron-interval-input"
+        type="text"
+        :placeholder="__('Define a custom pattern with cron syntax')"
+        required="true"
+        v-model="cronInterval"
+        :name="inputNameAttribute"
+        :disabled="!isEditable"
+      />
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/pipeline_schedules/components/pipeline_schedules_callout.js b/app/assets/javascripts/pipeline_schedules/components/pipeline_schedules_callout.js
index 5109b110b31fe8728f5bb6feac591207fbd2776a..c827b7402dcd694207e7580af3e8a958fd7c919a 100644
--- a/app/assets/javascripts/pipeline_schedules/components/pipeline_schedules_callout.js
+++ b/app/assets/javascripts/pipeline_schedules/components/pipeline_schedules_callout.js
@@ -1,6 +1,10 @@
+import Vue from 'vue';
 import Cookies from 'js-cookie';
+import Translate from '../../vue_shared/translate';
 import illustrationSvg from '../icons/intro_illustration.svg';
 
+Vue.use(Translate);
+
 const cookieKey = 'pipeline_schedules_callout_dismissed';
 
 export default {
@@ -29,20 +33,18 @@ export default {
         </button>
         <div class="svg-container" v-html="illustrationSvg"></div>
         <div class="user-callout-copy">
-          <h4>Scheduling Pipelines</h4>
+          <h4>{{ __('Scheduling Pipelines') }}</h4>
           <p>
-              The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags.
-              Those scheduled pipelines will inherit limited project access based on their associated user.
+              {{ __('The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user.') }}
           </p>
-          <p> Learn more in the
+          <p> {{ __('Learn more in the') }}
             <a
               :href="docsUrl"
               target="_blank"
-              rel="nofollow">pipeline schedules documentation</a>. <!-- oneline to prevent extra space before period -->
+              rel="nofollow">{{ s__('Learn more in the|pipeline schedules documentation') }}</a>. <!-- oneline to prevent extra space before period -->
           </p>
         </div>
       </div>
     </div>
   `,
 };
-
diff --git a/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js b/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js
index c60e77deccedf50d1d65fde6556d5d08f2331ec2..b424e7f205d43825ecb1a1477330a8e7a6c08776 100644
--- a/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js
+++ b/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js
@@ -1,20 +1,41 @@
 import Vue from 'vue';
-import IntervalPatternInput from './components/interval_pattern_input';
+import Translate from '../vue_shared/translate';
+import intervalPatternInput from './components/interval_pattern_input.vue';
 import TimezoneDropdown from './components/timezone_dropdown';
 import TargetBranchDropdown from './components/target_branch_dropdown';
 
-document.addEventListener('DOMContentLoaded', () => {
-  const IntervalPatternInputComponent = Vue.extend(IntervalPatternInput);
+Vue.use(Translate);
+
+function initIntervalPatternInput() {
   const intervalPatternMount = document.getElementById('interval-pattern-input');
   const initialCronInterval = intervalPatternMount ? intervalPatternMount.dataset.initialInterval : '';
 
-  new IntervalPatternInputComponent({
-    propsData: {
-      initialCronInterval,
+  return new Vue({
+    el: intervalPatternMount,
+    components: {
+      intervalPatternInput,
     },
-  }).$mount(intervalPatternMount);
+    render(createElement) {
+      return createElement('interval-pattern-input', {
+        props: {
+          initialCronInterval,
+        },
+      });
+    },
+  });
+}
+
+document.addEventListener('DOMContentLoaded', () => {
+  /* Most of the form is written in haml, but for fields with more complex behaviors,
+   * you should mount individual Vue components here. If at some point components need
+   * to share state, it may make sense to refactor the whole form to Vue */
+
+  initIntervalPatternInput();
+
+  // Initialize non-Vue JS components in the form
 
   const formElement = document.getElementById('new-pipeline-schedule-form');
+
   gl.timezoneDropdown = new TimezoneDropdown();
   gl.targetBranchDropdown = new TargetBranchDropdown();
   gl.pipelineScheduleFieldErrors = new gl.GlFieldErrors(formElement);
diff --git a/app/assets/javascripts/pipelines/components/async_button.vue b/app/assets/javascripts/pipelines/components/async_button.vue
index 37a6f02d8fda7811bd5a9cac7afa8a1cb6fd4ae5..16cc0761fc17186d6611e34194dee1c1458ebe2d 100644
--- a/app/assets/javascripts/pipelines/components/async_button.vue
+++ b/app/assets/javascripts/pipelines/components/async_button.vue
@@ -1,9 +1,9 @@
 <script>
 /* eslint-disable no-new, no-alert */
-/* global Flash */
-import '~/flash';
+
 import eventHub from '../event_hub';
 import loadingIcon from '../../vue_shared/components/loading_icon.vue';
+import tooltip from '../../vue_shared/directives/tooltip';
 
 export default {
   props: {
@@ -11,53 +11,42 @@ export default {
       type: String,
       required: true,
     },
-
-    service: {
-      type: Object,
-      required: true,
-    },
-
     title: {
       type: String,
       required: true,
     },
-
     icon: {
       type: String,
       required: true,
     },
-
     cssClass: {
       type: String,
       required: true,
     },
-
     confirmActionMessage: {
       type: String,
       required: false,
     },
   },
-
+  directives: {
+    tooltip,
+  },
   components: {
     loadingIcon,
   },
-
   data() {
     return {
       isLoading: false,
     };
   },
-
   computed: {
     iconClass() {
       return `fa fa-${this.icon}`;
     },
-
     buttonClass() {
-      return `btn has-tooltip ${this.cssClass}`;
+      return `btn ${this.cssClass}`;
     },
   },
-
   methods: {
     onClick() {
       if (this.confirmActionMessage && confirm(this.confirmActionMessage)) {
@@ -66,21 +55,10 @@ export default {
         this.makeRequest();
       }
     },
-
     makeRequest() {
       this.isLoading = true;
 
-      $(this.$el).tooltip('destroy');
-
-      this.service.postAction(this.endpoint)
-        .then(() => {
-          this.isLoading = false;
-          eventHub.$emit('refreshPipelines');
-        })
-        .catch(() => {
-          this.isLoading = false;
-          new Flash('An error occured while making the request.');
-        });
+      eventHub.$emit('postAction', this.endpoint);
     },
   },
 };
@@ -88,6 +66,7 @@ export default {
 
 <template>
   <button
+    v-tooltip
     type="button"
     @click="onClick"
     :class="buttonClass"
@@ -98,7 +77,8 @@ export default {
     :disabled="isLoading">
     <i
       :class="iconClass"
-      aria-hidden="true" />
+      aria-hidden="true">
+    </i>
     <loading-icon v-if="isLoading" />
   </button>
 </template>
diff --git a/app/assets/javascripts/pipelines/components/graph/action_component.vue b/app/assets/javascripts/pipelines/components/graph/action_component.vue
index 1f9e3d3977938ede3ac250cd41718c16b0432f03..54227425d2a0652fbff67c6336edd6919ce18603 100644
--- a/app/assets/javascripts/pipelines/components/graph/action_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/action_component.vue
@@ -1,6 +1,6 @@
 <script>
   import getActionIcon from '../../../vue_shared/ci_action_icons';
-  import tooltipMixin from '../../../vue_shared/mixins/tooltip';
+  import tooltip from '../../../vue_shared/directives/tooltip';
 
   /**
    * Renders either a cancel, retry or play icon pointing to the given path.
@@ -29,9 +29,9 @@
       },
     },
 
-    mixins: [
-      tooltipMixin,
-    ],
+    directives: {
+      tooltip,
+    },
 
     computed: {
       actionIconSvg() {
@@ -46,12 +46,11 @@
 </script>
 <template>
   <a
+    v-tooltip
     :data-method="actionMethod"
     :title="tooltipText"
     :href="link"
-    ref="tooltip"
     class="ci-action-icon-container"
-    data-toggle="tooltip"
     data-container="body">
 
     <i
diff --git a/app/assets/javascripts/pipelines/components/graph/dropdown_action_component.vue b/app/assets/javascripts/pipelines/components/graph/dropdown_action_component.vue
index 19cafff4e1c6e098f6d0ac6be9eb9a68625d9d5c..18fe1847eef0b52aab6e6f7cb82b9ca512742940 100644
--- a/app/assets/javascripts/pipelines/components/graph/dropdown_action_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/dropdown_action_component.vue
@@ -1,6 +1,6 @@
 <script>
   import getActionIcon from '../../../vue_shared/ci_action_icons';
-  import tooltipMixin from '../../../vue_shared/mixins/tooltip';
+  import tooltip from '../../../vue_shared/directives/tooltip';
 
   /**
    * Renders either a cancel, retry or play icon pointing to the given path.
@@ -29,9 +29,9 @@
       },
     },
 
-    mixins: [
-      tooltipMixin,
-    ],
+    directives: {
+      tooltip,
+    },
 
     computed: {
       actionIconSvg() {
@@ -42,13 +42,12 @@
 </script>
 <template>
   <a
+    v-tooltip
     :data-method="actionMethod"
     :title="tooltipText"
     :href="link"
-    ref="tooltip"
     rel="nofollow"
     class="ci-action-icon-wrapper js-ci-status-icon"
-    data-toggle="tooltip"
     data-container="body"
     v-html="actionIconSvg"
     aria-label="Job's action">
diff --git a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue
index d597af8dfb5f8f4ad9e03b7635a29c7ba97ec5ab..2944689a5a723f46a0e3a0a62cfe0f6d7cee0954 100644
--- a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue
@@ -1,7 +1,7 @@
 <script>
   import jobNameComponent from './job_name_component.vue';
   import jobComponent from './job_component.vue';
-  import tooltipMixin from '../../../vue_shared/mixins/tooltip';
+  import tooltip from '../../../vue_shared/directives/tooltip';
 
   /**
    * Renders the dropdown for the pipeline graph.
@@ -34,9 +34,9 @@
       },
     },
 
-    mixins: [
-      tooltipMixin,
-    ],
+    directives: {
+      tooltip,
+    },
 
     components: {
       jobComponent,
@@ -53,12 +53,12 @@
 <template>
   <div>
     <button
+      v-tooltip
       type="button"
       data-toggle="dropdown"
       data-container="body"
       class="dropdown-menu-toggle build-content"
-      :title="tooltipText"
-      ref="tooltip">
+      :title="tooltipText">
 
       <job-name-component
         :name="job.name"
diff --git a/app/assets/javascripts/pipelines/components/graph/job_component.vue b/app/assets/javascripts/pipelines/components/graph/job_component.vue
index b39c936101e3f989de65a8e78a808b1c573bcc06..1f5ed3f1074d8a6c11238257c86af464e63cfd65 100644
--- a/app/assets/javascripts/pipelines/components/graph/job_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/job_component.vue
@@ -2,7 +2,7 @@
   import actionComponent from './action_component.vue';
   import dropdownActionComponent from './dropdown_action_component.vue';
   import jobNameComponent from './job_name_component.vue';
-  import tooltipMixin from '../../../vue_shared/mixins/tooltip';
+  import tooltip from '../../../vue_shared/directives/tooltip';
 
   /**
    * Renders the badge for the pipeline graph and the job's dropdown.
@@ -54,9 +54,9 @@
       jobNameComponent,
     },
 
-    mixins: [
-      tooltipMixin,
-    ],
+    directives: {
+      tooltip,
+    },
 
     computed: {
       tooltipText() {
@@ -77,12 +77,11 @@
 <template>
   <div>
     <a
+      v-tooltip
       v-if="job.status.details_path"
       :href="job.status.details_path"
       :title="tooltipText"
       :class="cssClassJobName"
-      ref="tooltip"
-      data-toggle="tooltip"
       data-container="body">
 
       <job-name-component
@@ -93,10 +92,9 @@
 
     <div
       v-else
+      v-tooltip
       :title="tooltipText"
       :class="cssClassJobName"
-      ref="tooltip"
-      data-toggle="tooltip"
       data-container="body">
 
       <job-name-component
diff --git a/app/assets/javascripts/pipelines/components/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipeline_url.vue
index 8333ec0fbc3563bc12418d94d0ec0e30e29a9edb..2ca5ac2912f544a3600dba358513db1f5097b074 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_url.vue
+++ b/app/assets/javascripts/pipelines/components/pipeline_url.vue
@@ -1,6 +1,6 @@
 <script>
 import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
-import tooltipMixin from '../../vue_shared/mixins/tooltip';
+import tooltip from '../../vue_shared/directives/tooltip';
 
 export default {
   props: {
@@ -12,9 +12,9 @@ export default {
   components: {
     userAvatarLink,
   },
-  mixins: [
-    tooltipMixin,
-  ],
+  directives: {
+    tooltip,
+  },
   computed: {
     user() {
       return this.pipeline.user;
@@ -45,16 +45,16 @@ export default {
     <div class="label-container">
       <span
         v-if="pipeline.flags.latest"
+        v-tooltip
         class="js-pipeline-url-latest label label-success"
-        title="Latest pipeline for this branch"
-        ref="tooltip">
+        title="Latest pipeline for this branch">
         latest
       </span>
       <span
         v-if="pipeline.flags.yaml_errors"
+        v-tooltip
         class="js-pipeline-url-yaml label label-danger"
-        :title="pipeline.yaml_errors"
-        ref="tooltip">
+        :title="pipeline.yaml_errors">
         yaml invalid
       </span>
       <span
diff --git a/app/assets/javascripts/pipelines/components/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines.vue
index fed42d23112622867ce9d4fffdb08db984ace252..01ae07aad65d4bbade118e0a9b5da620df4a7adf 100644
--- a/app/assets/javascripts/pipelines/components/pipelines.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines.vue
@@ -1,15 +1,9 @@
 <script>
-  import Visibility from 'visibilityjs';
   import PipelinesService from '../services/pipelines_service';
-  import eventHub from '../event_hub';
-  import pipelinesTableComponent from '../../vue_shared/components/pipelines_table.vue';
+  import pipelinesMixin from '../mixins/pipelines';
   import tablePagination from '../../vue_shared/components/table_pagination.vue';
-  import emptyState from './empty_state.vue';
-  import errorState from './error_state.vue';
   import navigationTabs from './navigation_tabs.vue';
   import navigationControls from './nav_controls.vue';
-  import loadingIcon from '../../vue_shared/components/loading_icon.vue';
-  import Poll from '../../lib/utils/poll';
 
   export default {
     props: {
@@ -20,13 +14,12 @@
     },
     components: {
       tablePagination,
-      pipelinesTableComponent,
-      emptyState,
-      errorState,
       navigationTabs,
       navigationControls,
-      loadingIcon,
     },
+    mixins: [
+      pipelinesMixin,
+    ],
     data() {
       const pipelinesData = document.querySelector('#pipelines-list-vue').dataset;
 
@@ -47,11 +40,6 @@
         state: this.store.state,
         apiScope: 'all',
         pagenum: 1,
-        isLoading: false,
-        hasError: false,
-        isMakingRequest: false,
-        updateGraphDropdown: false,
-        hasMadeRequest: false,
       };
     },
     computed: {
@@ -62,9 +50,6 @@
         const scope = gl.utils.getParameterByName('scope');
         return scope === null ? 'all' : scope;
       },
-      shouldRenderErrorState() {
-        return this.hasError && !this.isLoading;
-      },
 
       /**
       * The empty state should only be rendered when the request is made to fetch all pipelines
@@ -106,7 +91,6 @@
           this.state.pipelines.length &&
           this.state.pageInfo.total > this.state.pageInfo.perPage;
       },
-
       hasCiEnabled() {
         return this.hasCi !== undefined;
       },
@@ -129,37 +113,7 @@
     },
     created() {
       this.service = new PipelinesService(this.endpoint);
-
-      const poll = new Poll({
-        resource: this.service,
-        method: 'getPipelines',
-        data: { page: this.pageParameter, scope: this.scopeParameter },
-        successCallback: this.successCallback,
-        errorCallback: this.errorCallback,
-        notificationCallback: this.setIsMakingRequest,
-      });
-
-      if (!Visibility.hidden()) {
-        this.isLoading = true;
-        poll.makeRequest();
-      } else {
-        // If tab is not visible we need to make the first request so we don't show the empty
-        // state without knowing if there are any pipelines
-        this.fetchPipelines();
-      }
-
-      Visibility.change(() => {
-        if (!Visibility.hidden()) {
-          poll.restart();
-        } else {
-          poll.stop();
-        }
-      });
-
-      eventHub.$on('refreshPipelines', this.fetchPipelines);
-    },
-    beforeDestroy() {
-      eventHub.$off('refreshPipelines');
+      this.requestData = { page: this.pageParameter, scope: this.scopeParameter };
     },
     methods: {
       /**
@@ -174,15 +128,6 @@
         return param;
       },
 
-      fetchPipelines() {
-        if (!this.isMakingRequest) {
-          this.isLoading = true;
-
-          this.service.getPipelines({ scope: this.scopeParameter, page: this.pageParameter })
-            .then(response => this.successCallback(response))
-            .catch(() => this.errorCallback());
-        }
-      },
       successCallback(resp) {
         const response = {
           headers: resp.headers,
@@ -190,33 +135,14 @@
         };
 
         this.store.storeCount(response.body.count);
-        this.store.storePipelines(response.body.pipelines);
         this.store.storePagination(response.headers);
-
-        this.isLoading = false;
-        this.updateGraphDropdown = true;
-        this.hasMadeRequest = true;
-      },
-
-      errorCallback() {
-        this.hasError = true;
-        this.isLoading = false;
-        this.updateGraphDropdown = false;
-      },
-
-      setIsMakingRequest(isMakingRequest) {
-        this.isMakingRequest = isMakingRequest;
-
-        if (isMakingRequest) {
-          this.updateGraphDropdown = false;
-        }
+        this.setCommonData(response.body.pipelines);
       },
     },
   };
 </script>
 <template>
   <div :class="cssClass">
-
     <div
       class="top-area scrolling-tabs-container inner-page-scroll-tabs"
       v-if="!isLoading && !shouldRenderEmptyState">
@@ -274,7 +200,6 @@
 
         <pipelines-table-component
           :pipelines="state.pipelines"
-          :service="service"
           :update-graph-dropdown="updateGraphDropdown"
           />
       </div>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_actions.vue
index 97b4de26214d57de05cb29643bcf0e84dbae28f1..01dfe51cc17de4af1cf6bd296410def04bc44396 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_actions.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_actions.vue
@@ -4,6 +4,7 @@
   import playIconSvg from 'icons/_icon_play.svg';
   import eventHub from '../event_hub';
   import loadingIcon from '../../vue_shared/components/loading_icon.vue';
+  import tooltip from '../../vue_shared/directives/tooltip';
 
   export default {
     props: {
@@ -11,10 +12,9 @@
         type: Array,
         required: true,
       },
-      service: {
-        type: Object,
-        required: true,
-      },
+    },
+    directives: {
+      tooltip,
     },
     components: {
       loadingIcon,
@@ -29,19 +29,9 @@
       onClickAction(endpoint) {
         this.isLoading = true;
 
-        $(this.$refs.tooltip).tooltip('destroy');
-
-        this.service.postAction(endpoint)
-        .then(() => {
-          this.isLoading = false;
-          eventHub.$emit('refreshPipelines');
-        })
-        .catch(() => {
-          this.isLoading = false;
-          // eslint-disable-next-line no-new
-          new Flash('An error occured while making the request.');
-        });
+        eventHub.$emit('postAction', endpoint);
       },
+
       isActionDisabled(action) {
         if (action.playable === undefined) {
           return false;
@@ -55,13 +45,13 @@
 <template>
   <div class="btn-group">
     <button
+      v-tooltip
       type="button"
-      class="dropdown-new btn btn-default has-tooltip js-pipeline-dropdown-manual-actions"
+      class="dropdown-new btn btn-default js-pipeline-dropdown-manual-actions"
       title="Manual job"
       data-toggle="dropdown"
       data-placement="top"
       aria-label="Manual job"
-      ref="tooltip"
       :disabled="isLoading">
       <span v-html="playIconSvg"></span>
       <i
diff --git a/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue b/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue
index b4520481cdc8d42ee9d872ae9e8eaa78107cf25b..b19bd509a00d6248ce70b1ada4555a8ab9d30f22 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue
@@ -1,5 +1,5 @@
 <script>
-  import tooltipMixin from '../../vue_shared/mixins/tooltip';
+  import tooltip from '../../vue_shared/directives/tooltip';
 
   export default {
     props: {
@@ -8,9 +8,9 @@
         required: true,
       },
     },
-    mixins: [
-      tooltipMixin,
-    ],
+    directives: {
+      tooltip,
+    },
   };
 </script>
 <template>
@@ -18,12 +18,12 @@
     class="btn-group"
     role="group">
     <button
+      v-tooltip
       class="dropdown-toggle btn btn-default build-artifacts js-pipeline-dropdown-download"
       title="Artifacts"
       data-placement="top"
       data-toggle="dropdown"
-      aria-label="Artifacts"
-      ref="tooltip">
+      aria-label="Artifacts">
       <i
         class="fa fa-download"
         aria-hidden="true">
diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table.vue b/app/assets/javascripts/pipelines/components/pipelines_table.vue
similarity index 93%
rename from app/assets/javascripts/vue_shared/components/pipelines_table.vue
rename to app/assets/javascripts/pipelines/components/pipelines_table.vue
index 884f1ce96890364a4dc3e3e8c92ec0abfd359a31..5088d92209fc7dc4565147c4dfa804f4a1f7918b 100644
--- a/app/assets/javascripts/vue_shared/components/pipelines_table.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_table.vue
@@ -12,10 +12,6 @@
         type: Array,
         required: true,
       },
-      service: {
-        type: Object,
-        required: true,
-      },
       updateGraphDropdown: {
         type: Boolean,
         required: false,
@@ -57,7 +53,6 @@
       v-for="model in pipelines"
       :key="model.id"
       :pipeline="model"
-      :service="service"
       :update-graph-dropdown="updateGraphDropdown"
     />
   </div>
diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
similarity index 91%
rename from app/assets/javascripts/vue_shared/components/pipelines_table_row.vue
rename to app/assets/javascripts/pipelines/components/pipelines_table_row.vue
index 4d5ebe2e9ededa67c2dfc3300256972534b6b9a9..c3f1c426d8a2d2a67c64ea108f77a4b1179f05bb 100644
--- a/app/assets/javascripts/vue_shared/components/pipelines_table_row.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
@@ -1,13 +1,13 @@
 <script>
 /* eslint-disable no-param-reassign */
-import asyncButtonComponent from '../../pipelines/components/async_button.vue';
-import pipelinesActionsComponent from '../../pipelines/components/pipelines_actions.vue';
-import pipelinesArtifactsComponent from '../../pipelines/components/pipelines_artifacts.vue';
-import ciBadge from './ci_badge_link.vue';
-import pipelineStage from '../../pipelines/components/stage.vue';
-import pipelineUrl from '../../pipelines/components/pipeline_url.vue';
-import pipelinesTimeago from '../../pipelines/components/time_ago.vue';
-import commitComponent from './commit.vue';
+import asyncButtonComponent from './async_button.vue';
+import pipelinesActionsComponent from './pipelines_actions.vue';
+import pipelinesArtifactsComponent from './pipelines_artifacts.vue';
+import ciBadge from '../../vue_shared/components/ci_badge_link.vue';
+import pipelineStage from './stage.vue';
+import pipelineUrl from './pipeline_url.vue';
+import pipelinesTimeago from './time_ago.vue';
+import commitComponent from '../../vue_shared/components/commit.vue';
 
 /**
  * Pipeline table row.
@@ -20,10 +20,6 @@ export default {
       type: Object,
       required: true,
     },
-    service: {
-      type: Object,
-      required: true,
-    },
     updateGraphDropdown: {
       type: Boolean,
       required: false,
@@ -271,7 +267,6 @@ export default {
         <pipelines-actions-component
           v-if="pipeline.details.manual_actions.length"
           :actions="pipeline.details.manual_actions"
-          :service="service"
           />
 
         <pipelines-artifacts-component
@@ -282,7 +277,6 @@ export default {
 
         <async-button-component
           v-if="pipeline.flags.retryable"
-          :service="service"
           :endpoint="pipeline.retry_path"
           css-class="js-pipelines-retry-button btn-default btn-retry"
           title="Retry"
@@ -291,7 +285,6 @@ export default {
 
         <async-button-component
           v-if="pipeline.flags.cancelable"
-          :service="service"
           :endpoint="pipeline.cancel_path"
           css-class="js-pipelines-cancel-button btn-remove"
           title="Cancel"
diff --git a/app/assets/javascripts/pipelines/components/stage.vue b/app/assets/javascripts/pipelines/components/stage.vue
index c05c76c9a644e0fcb93b4d549ebf216dd80d6bdc..87b2725a04500c33a5d76e10885a96a70c210d91 100644
--- a/app/assets/javascripts/pipelines/components/stage.vue
+++ b/app/assets/javascripts/pipelines/components/stage.vue
@@ -16,7 +16,7 @@
 /* global Flash */
 import { borderlessStatusIconEntityMap } from '../../vue_shared/ci_status_icons';
 import loadingIcon from '../../vue_shared/components/loading_icon.vue';
-import tooltipMixin from '../../vue_shared/mixins/tooltip';
+import tooltip from '../../vue_shared/directives/tooltip';
 
 export default {
   props: {
@@ -32,15 +32,14 @@ export default {
     },
   },
 
-  mixins: [
-    tooltipMixin,
-  ],
+  directives: {
+    tooltip,
+  },
 
   data() {
     return {
       isLoading: false,
       dropdownContent: '',
-      endpoint: this.stage.dropdown_path,
     };
   },
 
@@ -73,7 +72,7 @@ export default {
     },
 
     fetchJobs() {
-      this.$http.get(this.endpoint)
+      this.$http.get(this.stage.dropdown_path)
         .then((response) => {
           this.dropdownContent = response.json().html;
           this.isLoading = false;
@@ -132,7 +131,7 @@ export default {
 <template>
   <div class="dropdown">
     <button
-      ref="tooltip"
+      v-tooltip
       :class="triggerButtonClass"
       @click="onClickStage"
       class="mini-pipeline-graph-dropdown-toggle js-builds-dropdown-button"
diff --git a/app/assets/javascripts/pipelines/components/time_ago.vue b/app/assets/javascripts/pipelines/components/time_ago.vue
index be3f32afa09eca71daee06456e0e6ae7a9dbe298..037684b4e7223a5e2641707d8283cb9afb889a6f 100644
--- a/app/assets/javascripts/pipelines/components/time_ago.vue
+++ b/app/assets/javascripts/pipelines/components/time_ago.vue
@@ -1,7 +1,7 @@
 <script>
   import iconTimerSvg from 'icons/_icon_timer.svg';
   import '../../lib/utils/datetime_utility';
-  import tooltipMixin from '../../vue_shared/mixins/tooltip';
+  import tooltip from '../../vue_shared/directives/tooltip';
   import timeagoMixin from '../../vue_shared/mixins/timeago';
 
   export default {
@@ -16,9 +16,11 @@
       },
     },
     mixins: [
-      tooltipMixin,
       timeagoMixin,
     ],
+    directives: {
+      tooltip,
+    },
     data() {
       return {
         iconTimerSvg,
@@ -81,7 +83,7 @@
         </i>
 
         <time
-          ref="tooltip"
+          v-tooltip
           data-placement="top"
           data-container="body"
           :title="tooltipTitle(finishedTime)">
diff --git a/app/assets/javascripts/pipelines/mixins/pipelines.js b/app/assets/javascripts/pipelines/mixins/pipelines.js
new file mode 100644
index 0000000000000000000000000000000000000000..9adc15e6266ac3f08f8165cdcd27a8a0dbf736cf
--- /dev/null
+++ b/app/assets/javascripts/pipelines/mixins/pipelines.js
@@ -0,0 +1,103 @@
+/* global Flash */
+import '~/flash';
+import Visibility from 'visibilityjs';
+import Poll from '../../lib/utils/poll';
+import emptyState from '../components/empty_state.vue';
+import errorState from '../components/error_state.vue';
+import loadingIcon from '../../vue_shared/components/loading_icon.vue';
+import pipelinesTableComponent from '../components/pipelines_table.vue';
+import eventHub from '../event_hub';
+
+export default {
+  components: {
+    pipelinesTableComponent,
+    errorState,
+    emptyState,
+    loadingIcon,
+  },
+  computed: {
+    shouldRenderErrorState() {
+      return this.hasError && !this.isLoading;
+    },
+  },
+  data() {
+    return {
+      isLoading: false,
+      hasError: false,
+      isMakingRequest: false,
+      updateGraphDropdown: false,
+      hasMadeRequest: false,
+    };
+  },
+  beforeMount() {
+    this.poll = new Poll({
+      resource: this.service,
+      method: 'getPipelines',
+      data: this.requestData ? this.requestData : undefined,
+      successCallback: this.successCallback,
+      errorCallback: this.errorCallback,
+      notificationCallback: this.setIsMakingRequest,
+    });
+
+    if (!Visibility.hidden()) {
+      this.isLoading = true;
+      this.poll.makeRequest();
+    } else {
+      // If tab is not visible we need to make the first request so we don't show the empty
+      // state without knowing if there are any pipelines
+      this.fetchPipelines();
+    }
+
+    Visibility.change(() => {
+      if (!Visibility.hidden()) {
+        this.poll.restart();
+      } else {
+        this.poll.stop();
+      }
+    });
+
+    eventHub.$on('refreshPipelines', this.fetchPipelines);
+    eventHub.$on('postAction', this.postAction);
+  },
+  beforeDestroy() {
+    eventHub.$off('refreshPipelines');
+    eventHub.$on('postAction', this.postAction);
+  },
+  destroyed() {
+    this.poll.stop();
+  },
+  methods: {
+    fetchPipelines() {
+      if (!this.isMakingRequest) {
+        this.isLoading = true;
+
+        this.service.getPipelines(this.requestData)
+          .then(response => this.successCallback(response))
+          .catch(() => this.errorCallback());
+      }
+    },
+    setCommonData(pipelines) {
+      this.store.storePipelines(pipelines);
+      this.isLoading = false;
+      this.updateGraphDropdown = true;
+      this.hasMadeRequest = true;
+    },
+    errorCallback() {
+      this.hasError = true;
+      this.isLoading = false;
+      this.updateGraphDropdown = false;
+    },
+    setIsMakingRequest(isMakingRequest) {
+      this.isMakingRequest = isMakingRequest;
+
+      if (isMakingRequest) {
+        this.updateGraphDropdown = false;
+      }
+    },
+    postAction(endpoint) {
+      this.service.postAction(endpoint)
+        .then(() => eventHub.$emit('refreshPipelines'))
+        .catch(() => new Flash('An error occured while making the request.'));
+    },
+  },
+};
diff --git a/app/assets/javascripts/preview_markdown.js b/app/assets/javascripts/preview_markdown.js
index 4a3df2fd465c8d73a3e368e5e0fcf22072eca760..141333b2b4d1af3514920569778d692b0a7a988f 100644
--- a/app/assets/javascripts/preview_markdown.js
+++ b/app/assets/javascripts/preview_markdown.js
@@ -3,7 +3,7 @@
 // MarkdownPreview
 //
 // Handles toggling the "Write" and "Preview" tab clicks, rendering the preview
-// (including the explanation of slash commands), and showing a warning when
+// (including the explanation of quick actions), and showing a warning when
 // more than `x` users are referenced.
 //
 (function () {
diff --git a/app/assets/javascripts/prometheus_metrics/constants.js b/app/assets/javascripts/prometheus_metrics/constants.js
new file mode 100644
index 0000000000000000000000000000000000000000..50f1248456e63d369e10df42b696b9d2b4ab8feb
--- /dev/null
+++ b/app/assets/javascripts/prometheus_metrics/constants.js
@@ -0,0 +1,5 @@
+export default {
+  EMPTY: 'empty',
+  LOADING: 'loading',
+  LIST: 'list',
+};
diff --git a/app/assets/javascripts/prometheus_metrics/index.js b/app/assets/javascripts/prometheus_metrics/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..a0c43c5abe17c30bb14541616eda45cfa3155b3f
--- /dev/null
+++ b/app/assets/javascripts/prometheus_metrics/index.js
@@ -0,0 +1,6 @@
+import PrometheusMetrics from './prometheus_metrics';
+
+$(() => {
+  const prometheusMetrics = new PrometheusMetrics('.js-prometheus-metrics-monitoring');
+  prometheusMetrics.loadActiveMetrics();
+});
diff --git a/app/assets/javascripts/prometheus_metrics/prometheus_metrics.js b/app/assets/javascripts/prometheus_metrics/prometheus_metrics.js
new file mode 100644
index 0000000000000000000000000000000000000000..ef4d6df5138f75e04b60371c9d0508783adb0eac
--- /dev/null
+++ b/app/assets/javascripts/prometheus_metrics/prometheus_metrics.js
@@ -0,0 +1,109 @@
+import PANEL_STATE from './constants';
+
+export default class PrometheusMetrics {
+  constructor(wrapperSelector) {
+    this.backOffRequestCounter = 0;
+
+    this.$wrapper = $(wrapperSelector);
+
+    this.$monitoredMetricsPanel = this.$wrapper.find('.js-panel-monitored-metrics');
+    this.$monitoredMetricsCount = this.$monitoredMetricsPanel.find('.js-monitored-count');
+    this.$monitoredMetricsLoading = this.$monitoredMetricsPanel.find('.js-loading-metrics');
+    this.$monitoredMetricsEmpty = this.$monitoredMetricsPanel.find('.js-empty-metrics');
+    this.$monitoredMetricsList = this.$monitoredMetricsPanel.find('.js-metrics-list');
+
+    this.$missingEnvVarPanel = this.$wrapper.find('.js-panel-missing-env-vars');
+    this.$panelToggle = this.$missingEnvVarPanel.find('.js-panel-toggle');
+    this.$missingEnvVarMetricCount = this.$missingEnvVarPanel.find('.js-env-var-count');
+    this.$missingEnvVarMetricsList = this.$missingEnvVarPanel.find('.js-missing-var-metrics-list');
+
+    this.activeMetricsEndpoint = this.$monitoredMetricsPanel.data('active-metrics');
+
+    this.$panelToggle.on('click', e => this.handlePanelToggle(e));
+  }
+
+  /* eslint-disable class-methods-use-this */
+  handlePanelToggle(e) {
+    const $toggleBtn = $(e.currentTarget);
+    const $currentPanelBody = $toggleBtn.closest('.panel').find('.panel-body');
+    $currentPanelBody.toggleClass('hidden');
+    if ($toggleBtn.hasClass('fa-caret-down')) {
+      $toggleBtn.removeClass('fa-caret-down').addClass('fa-caret-right');
+    } else {
+      $toggleBtn.removeClass('fa-caret-right').addClass('fa-caret-down');
+    }
+  }
+
+  showMonitoringMetricsPanelState(stateName) {
+    switch (stateName) {
+      case PANEL_STATE.LOADING:
+        this.$monitoredMetricsLoading.removeClass('hidden');
+        this.$monitoredMetricsEmpty.addClass('hidden');
+        this.$monitoredMetricsList.addClass('hidden');
+        break;
+      case PANEL_STATE.LIST:
+        this.$monitoredMetricsLoading.addClass('hidden');
+        this.$monitoredMetricsEmpty.addClass('hidden');
+        this.$monitoredMetricsList.removeClass('hidden');
+        break;
+      default:
+        this.$monitoredMetricsLoading.addClass('hidden');
+        this.$monitoredMetricsEmpty.removeClass('hidden');
+        this.$monitoredMetricsList.addClass('hidden');
+        break;
+    }
+  }
+
+  populateActiveMetrics(metrics) {
+    let totalMonitoredMetrics = 0;
+    let totalMissingEnvVarMetrics = 0;
+
+    metrics.forEach((metric) => {
+      this.$monitoredMetricsList.append(`<li>${metric.group}<span class="badge">${metric.active_metrics}</span></li>`);
+      totalMonitoredMetrics += metric.active_metrics;
+      if (metric.metrics_missing_requirements > 0) {
+        this.$missingEnvVarMetricsList.append(`<li>${metric.group}</li>`);
+        totalMissingEnvVarMetrics += 1;
+      }
+    });
+
+    this.$monitoredMetricsCount.text(totalMonitoredMetrics);
+    this.showMonitoringMetricsPanelState(PANEL_STATE.LIST);
+
+    if (totalMissingEnvVarMetrics > 0) {
+      this.$missingEnvVarPanel.removeClass('hidden');
+      this.$missingEnvVarPanel.find('.flash-container').off('click');
+      this.$missingEnvVarMetricCount.text(totalMissingEnvVarMetrics);
+    }
+  }
+
+  loadActiveMetrics() {
+    this.showMonitoringMetricsPanelState(PANEL_STATE.LOADING);
+    gl.utils.backOff((next, stop) => {
+      $.getJSON(this.activeMetricsEndpoint)
+        .done((res) => {
+          if (res && res.success) {
+            stop(res);
+          } else {
+            this.backOffRequestCounter = this.backOffRequestCounter += 1;
+            if (this.backOffRequestCounter < 3) {
+              next();
+            } else {
+              stop(res);
+            }
+          }
+        })
+        .fail(stop);
+    })
+    .then((res) => {
+      if (res && res.data && res.data.length) {
+        this.populateActiveMetrics(res.data);
+      } else {
+        this.showMonitoringMetricsPanelState(PANEL_STATE.EMPTY);
+      }
+    })
+    .catch(() => {
+      this.showMonitoringMetricsPanelState(PANEL_STATE.EMPTY);
+    });
+  }
+}
diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js
index b71c3097706e665b8dffc9d82c4fb2fb1f1e8c03..d8f1fe10b269484f0fafd40936032062ea744547 100644
--- a/app/assets/javascripts/right_sidebar.js
+++ b/app/assets/javascripts/right_sidebar.js
@@ -1,12 +1,14 @@
 /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-unused-vars, consistent-return, one-var, one-var-declaration-per-line, quotes, prefer-template, object-shorthand, comma-dangle, no-else-return, no-param-reassign, max-len */
 
 import Cookies from 'js-cookie';
+import SidebarHeightManager from './sidebar_height_manager';
 
 (function() {
   this.Sidebar = (function() {
     function Sidebar(currentUser) {
       this.toggleTodo = this.toggleTodo.bind(this);
       this.sidebar = $('aside');
+
       this.removeListeners();
       this.addEventListeners();
     }
@@ -20,15 +22,14 @@ import Cookies from 'js-cookie';
     };
 
     Sidebar.prototype.addEventListeners = function() {
+      SidebarHeightManager.init();
       const $document = $(document);
-      const throttledSetSidebarHeight = _.throttle(this.setSidebarHeight, 10);
 
       this.sidebar.on('click', '.sidebar-collapsed-icon', this, this.sidebarCollapseClicked);
       $('.dropdown').on('hidden.gl.dropdown', this, this.onSidebarDropdownHidden);
       $('.dropdown').on('loading.gl.dropdown', this.sidebarDropdownLoading);
       $('.dropdown').on('loaded.gl.dropdown', this.sidebarDropdownLoaded);
-      $(window).on('resize', () => throttledSetSidebarHeight());
-      $document.on('scroll', () => throttledSetSidebarHeight());
+
       $document.on('click', '.js-sidebar-toggle', function(e, triggered) {
         var $allGutterToggleIcons, $this, $thisIcon;
         e.preventDefault();
@@ -206,17 +207,6 @@ import Cookies from 'js-cookie';
       }
     };
 
-    Sidebar.prototype.setSidebarHeight = function() {
-      const $navHeight = $('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight() + $('.sub-nav-scroll').outerHeight();
-      const $rightSidebar = $('.js-right-sidebar');
-      const diff = $navHeight - $(window).scrollTop();
-      if (diff > 0) {
-        $rightSidebar.outerHeight($(window).height() - diff);
-      } else {
-        $rightSidebar.outerHeight('100%');
-      }
-    };
-
     Sidebar.prototype.isOpen = function() {
       return this.sidebar.is('.right-sidebar-expanded');
     };
diff --git a/app/assets/javascripts/settings_panels.js b/app/assets/javascripts/settings_panels.js
index e67f449e1a2a5501072f3215cce5acd74d3a5bdb..7fa5996d60021b785b45c92931b005ab1281b459 100644
--- a/app/assets/javascripts/settings_panels.js
+++ b/app/assets/javascripts/settings_panels.js
@@ -1,11 +1,28 @@
+function expandSectionParent($section, $content) {
+  $section.addClass('expanded');
+  $content.off('animationend.expandSectionParent');
+}
+
 function expandSection($section) {
-  $section.find('.js-settings-toggle').text('Close');
-  $section.find('.settings-content').addClass('expanded').off('scroll').scrollTop(0);
+  $section.find('.js-settings-toggle').text('Collapse');
+
+  const $content = $section.find('.settings-content');
+  $content.addClass('expanded').off('scroll.expandSection').scrollTop(0);
+
+  if ($content.hasClass('no-animate')) {
+    expandSectionParent($section, $content);
+  } else {
+    $content.on('animationend.expandSectionParent', () => expandSectionParent($section, $content));
+  }
 }
 
 function closeSection($section) {
   $section.find('.js-settings-toggle').text('Expand');
-  $section.find('.settings-content').removeClass('expanded').on('scroll', () => expandSection($section));
+
+  const $content = $section.find('.settings-content');
+  $content.removeClass('expanded').on('scroll.expandSection', () => expandSection($section));
+
+  $section.removeClass('expanded');
 }
 
 function toggleSection($section) {
@@ -21,7 +38,7 @@ function toggleSection($section) {
 export default function initSettingsPanels() {
   $('.settings').each((i, elm) => {
     const $section = $(elm);
-    $section.on('click', '.js-settings-toggle', () => toggleSection($section));
-    $section.find('.settings-content:not(.expanded)').on('scroll', () => expandSection($section));
+    $section.on('click.toggleSection', '.js-settings-toggle', () => toggleSection($section));
+    $section.find('.settings-content:not(.expanded)').on('scroll.expandSection', () => expandSection($section));
   });
 }
diff --git a/app/assets/javascripts/sidebar/components/assignees/assignee_title.js b/app/assets/javascripts/sidebar/components/assignees/assignee_title.js
index a9ad3708514b70303f1db21fabbc18515b587530..5a6e47e566e00e70ac5b45ddddec1efc0ac6c2eb 100644
--- a/app/assets/javascripts/sidebar/components/assignees/assignee_title.js
+++ b/app/assets/javascripts/sidebar/components/assignees/assignee_title.js
@@ -14,6 +14,11 @@ export default {
       type: Boolean,
       required: true,
     },
+    showToggle: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
   },
   computed: {
     assigneeTitle() {
@@ -36,6 +41,19 @@ export default {
       >
         Edit
       </a>
+      <a
+        v-if="showToggle"
+        aria-label="Toggle sidebar"
+        class="gutter-toggle pull-right js-sidebar-toggle"
+        href="#"
+        role="button"
+      >
+        <i
+          aria-hidden="true"
+          data-hidden="true"
+          class="fa fa-angle-double-right"
+        />
+      </a>
     </div>
   `,
 };
diff --git a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.js b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.js
index da4abf0b68fa0aff90a410f4dc2aab039b3a2f19..f83c3b037ed17d9b699432165807a19e46e141e6 100644
--- a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.js
+++ b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.js
@@ -64,6 +64,7 @@ export default {
   },
   beforeMount() {
     this.field = this.$el.dataset.field;
+    this.signedIn = typeof this.$el.dataset.signedIn !== 'undefined';
   },
   template: `
     <div>
@@ -71,6 +72,7 @@ export default {
         :number-of-assignees="store.assignees.length"
         :loading="loading || store.isFetching.assignees"
         :editable="store.editable"
+        :show-toggle="!signedIn"
       />
       <assignees
         v-if="!store.isFetching.assignees"
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/help_state.js b/app/assets/javascripts/sidebar/components/time_tracking/help_state.js
index b2a77462fe0d45ad608dd77a18f4abe8b96db037..142ad4375092b6373dee591651097e761da016eb 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/help_state.js
+++ b/app/assets/javascripts/sidebar/components/time_tracking/help_state.js
@@ -15,10 +15,10 @@ export default {
     <div class="time-tracking-help-state">
       <div class="time-tracking-info">
         <h4>
-          Track time with slash commands
+          Track time with quick actions
         </h4>
         <p>
-          Slash commands can be used in the issues description and comment boxes.
+          Quick actions can be used in the issues description and comment boxes.
         </p>
         <p>
           <code>
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js b/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js
index 244b67b3ad9266ce984d2f2a8421d0b2fe9a99d4..650e935b1165b3ae0e63bed9a8fc1c7be9770686 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js
+++ b/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js
@@ -16,10 +16,10 @@ export default {
     'issuable-time-tracker': timeTracker,
   },
   methods: {
-    listenForSlashCommands() {
-      $(document).on('ajax:success', '.gfm-form', this.slashCommandListened);
+    listenForQuickActions() {
+      $(document).on('ajax:success', '.gfm-form', this.quickActionListened);
     },
-    slashCommandListened(e, data) {
+    quickActionListened(e, data) {
       const subscribedCommands = ['spend_time', 'time_estimate'];
       let changedCommands;
       if (data !== undefined) {
@@ -35,7 +35,7 @@ export default {
     },
   },
   mounted() {
-    this.listenForSlashCommands();
+    this.listenForQuickActions();
   },
   template: `
     <div class="block">
diff --git a/app/assets/javascripts/sidebar_height_manager.js b/app/assets/javascripts/sidebar_height_manager.js
new file mode 100644
index 0000000000000000000000000000000000000000..022415f22b2cffb08f8707772b230f5180ae5717
--- /dev/null
+++ b/app/assets/javascripts/sidebar_height_manager.js
@@ -0,0 +1,33 @@
+export default {
+  init() {
+    if (!this.initialized) {
+      this.$window = $(window);
+      this.$rightSidebar = $('.js-right-sidebar');
+      this.$navHeight = $('.navbar-gitlab').outerHeight() +
+        $('.layout-nav').outerHeight() +
+        $('.sub-nav-scroll').outerHeight();
+
+      const throttledSetSidebarHeight = _.throttle(() => this.setSidebarHeight(), 20);
+      const debouncedSetSidebarHeight = _.debounce(() => this.setSidebarHeight(), 200);
+
+      this.$window.on('scroll', throttledSetSidebarHeight);
+      this.$window.on('resize', debouncedSetSidebarHeight);
+      this.initialized = true;
+    }
+  },
+
+  setSidebarHeight() {
+    const currentScrollDepth = window.pageYOffset || 0;
+    const diff = this.$navHeight - currentScrollDepth;
+
+    if (diff > 0) {
+      const newSidebarHeight = window.innerHeight - diff;
+      this.$rightSidebar.outerHeight(newSidebarHeight);
+      this.sidebarHeightIsCustom = true;
+    } else if (this.sidebarHeightIsCustom) {
+      this.$rightSidebar.outerHeight('100%');
+      this.sidebarHeightIsCustom = false;
+    }
+  },
+};
+
diff --git a/app/assets/javascripts/signin_tabs_memoizer.js b/app/assets/javascripts/signin_tabs_memoizer.js
index 2587facc582257b8c626281c89e98e20f3607f2a..3997a695d153f4d5c7505fce72503b40eef4b226 100644
--- a/app/assets/javascripts/signin_tabs_memoizer.js
+++ b/app/assets/javascripts/signin_tabs_memoizer.js
@@ -2,56 +2,54 @@
 /* eslint no-new: "off" */
 import AccessorUtilities from './lib/utils/accessor';
 
-((global) => {
-  /**
-   * Memorize the last selected tab after reloading a page.
-   * Does that setting the current selected tab in the localStorage
-   */
-  class ActiveTabMemoizer {
-    constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.nav-tabs' } = {}) {
-      this.currentTabKey = currentTabKey;
-      this.tabSelector = tabSelector;
-      this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
-
-      this.bootstrap();
-    }
-
-    bootstrap() {
-      const tabs = document.querySelectorAll(this.tabSelector);
-      if (tabs.length > 0) {
-        tabs[0].addEventListener('click', (e) => {
-          if (e.target && e.target.nodeName === 'A') {
-            const anchorName = e.target.getAttribute('href');
-            this.saveData(anchorName);
-          }
-        });
-      }
+/**
+ * Memorize the last selected tab after reloading a page.
+ * Does that setting the current selected tab in the localStorage
+ */
+class ActiveTabMemoizer {
+  constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.nav-tabs' } = {}) {
+    this.currentTabKey = currentTabKey;
+    this.tabSelector = tabSelector;
+    this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
+
+    this.bootstrap();
+  }
 
-      this.showTab();
+  bootstrap() {
+    const tabs = document.querySelectorAll(this.tabSelector);
+    if (tabs.length > 0) {
+      tabs[0].addEventListener('click', (e) => {
+        if (e.target && e.target.nodeName === 'A') {
+          const anchorName = e.target.getAttribute('href');
+          this.saveData(anchorName);
+        }
+      });
     }
 
-    showTab() {
-      const anchorName = this.readData();
-      if (anchorName) {
-        const tab = document.querySelector(`${this.tabSelector} a[href="${anchorName}"]`);
-        if (tab) {
-          tab.click();
-        }
+    this.showTab();
+  }
+
+  showTab() {
+    const anchorName = this.readData();
+    if (anchorName) {
+      const tab = document.querySelector(`${this.tabSelector} a[href="${anchorName}"]`);
+      if (tab) {
+        tab.click();
       }
     }
+  }
 
-    saveData(val) {
-      if (!this.isLocalStorageAvailable) return undefined;
+  saveData(val) {
+    if (!this.isLocalStorageAvailable) return undefined;
 
-      return window.localStorage.setItem(this.currentTabKey, val);
-    }
+    return window.localStorage.setItem(this.currentTabKey, val);
+  }
 
-    readData() {
-      if (!this.isLocalStorageAvailable) return null;
+  readData() {
+    if (!this.isLocalStorageAvailable) return null;
 
-      return window.localStorage.getItem(this.currentTabKey);
-    }
+    return window.localStorage.getItem(this.currentTabKey);
   }
+}
 
-  global.ActiveTabMemoizer = ActiveTabMemoizer;
-})(window);
+window.ActiveTabMemoizer = ActiveTabMemoizer;
diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js
index c44892dae3d21cce0ae299479fd0e399774e87f2..00d04ce0c33865b2dc9ca29dcafd189a413d9cfe 100644
--- a/app/assets/javascripts/single_file_diff.js
+++ b/app/assets/javascripts/single_file_diff.js
@@ -1,96 +1,98 @@
 /* eslint-disable func-names, prefer-arrow-callback, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, one-var-declaration-per-line, consistent-return, no-param-reassign, max-len */
 
-(function() {
-  window.SingleFileDiff = (function() {
-    var COLLAPSED_HTML, ERROR_HTML, LOADING_HTML, WRAPPER;
+import FilesCommentButton from './files_comment_button';
 
-    WRAPPER = '<div class="diff-content"></div>';
+window.SingleFileDiff = (function() {
+  var COLLAPSED_HTML, ERROR_HTML, LOADING_HTML, WRAPPER;
 
-    LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>';
+  WRAPPER = '<div class="diff-content"></div>';
 
-    ERROR_HTML = '<div class="nothing-here-block"><i class="fa fa-warning"></i> Could not load diff</div>';
+  LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>';
 
-    COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <a class="click-to-expand">Click to expand it.</a></div>';
+  ERROR_HTML = '<div class="nothing-here-block"><i class="fa fa-warning"></i> Could not load diff</div>';
 
-    function SingleFileDiff(file) {
-      this.file = file;
-      this.toggleDiff = this.toggleDiff.bind(this);
-      this.content = $('.diff-content', this.file);
-      this.$toggleIcon = $('.diff-toggle-caret', this.file);
-      this.diffForPath = this.content.find('[data-diff-for-path]').data('diff-for-path');
-      this.isOpen = !this.diffForPath;
-      if (this.diffForPath) {
-        this.collapsedContent = this.content;
-        this.loadingContent = $(WRAPPER).addClass('loading').html(LOADING_HTML).hide();
-        this.content = null;
-        this.collapsedContent.after(this.loadingContent);
-        this.$toggleIcon.addClass('fa-caret-right');
-      } else {
-        this.collapsedContent = $(WRAPPER).html(COLLAPSED_HTML).hide();
-        this.content.after(this.collapsedContent);
-        this.$toggleIcon.addClass('fa-caret-down');
-      }
+  COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <a class="click-to-expand">Click to expand it.</a></div>';
 
-      $('.js-file-title, .click-to-expand', this.file).on('click', (function (e) {
-        this.toggleDiff($(e.target));
-      }).bind(this));
+  function SingleFileDiff(file) {
+    this.file = file;
+    this.toggleDiff = this.toggleDiff.bind(this);
+    this.content = $('.diff-content', this.file);
+    this.$toggleIcon = $('.diff-toggle-caret', this.file);
+    this.diffForPath = this.content.find('[data-diff-for-path]').data('diff-for-path');
+    this.isOpen = !this.diffForPath;
+    if (this.diffForPath) {
+      this.collapsedContent = this.content;
+      this.loadingContent = $(WRAPPER).addClass('loading').html(LOADING_HTML).hide();
+      this.content = null;
+      this.collapsedContent.after(this.loadingContent);
+      this.$toggleIcon.addClass('fa-caret-right');
+    } else {
+      this.collapsedContent = $(WRAPPER).html(COLLAPSED_HTML).hide();
+      this.content.after(this.collapsedContent);
+      this.$toggleIcon.addClass('fa-caret-down');
     }
 
-    SingleFileDiff.prototype.toggleDiff = function($target, cb) {
-      if (!$target.hasClass('js-file-title') && !$target.hasClass('click-to-expand') && !$target.hasClass('diff-toggle-caret')) return;
-      this.isOpen = !this.isOpen;
-      if (!this.isOpen && !this.hasError) {
-        this.content.hide();
-        this.$toggleIcon.addClass('fa-caret-right').removeClass('fa-caret-down');
-        this.collapsedContent.show();
-        if (typeof gl.diffNotesCompileComponents !== 'undefined') {
-          gl.diffNotesCompileComponents();
+    $('.js-file-title, .click-to-expand', this.file).on('click', (function (e) {
+      this.toggleDiff($(e.target));
+    }).bind(this));
+  }
+
+  SingleFileDiff.prototype.toggleDiff = function($target, cb) {
+    if (!$target.hasClass('js-file-title') && !$target.hasClass('click-to-expand') && !$target.hasClass('diff-toggle-caret')) return;
+    this.isOpen = !this.isOpen;
+    if (!this.isOpen && !this.hasError) {
+      this.content.hide();
+      this.$toggleIcon.addClass('fa-caret-right').removeClass('fa-caret-down');
+      this.collapsedContent.show();
+      if (typeof gl.diffNotesCompileComponents !== 'undefined') {
+        gl.diffNotesCompileComponents();
+      }
+    } else if (this.content) {
+      this.collapsedContent.hide();
+      this.content.show();
+      this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right');
+      if (typeof gl.diffNotesCompileComponents !== 'undefined') {
+        gl.diffNotesCompileComponents();
+      }
+    } else {
+      this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right');
+      return this.getContentHTML(cb);
+    }
+  };
+
+  SingleFileDiff.prototype.getContentHTML = function(cb) {
+    this.collapsedContent.hide();
+    this.loadingContent.show();
+    $.get(this.diffForPath, (function(_this) {
+      return function(data) {
+        _this.loadingContent.hide();
+        if (data.html) {
+          _this.content = $(data.html);
+          _this.content.syntaxHighlight();
+        } else {
+          _this.hasError = true;
+          _this.content = $(ERROR_HTML);
         }
-      } else if (this.content) {
-        this.collapsedContent.hide();
-        this.content.show();
-        this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right');
+        _this.collapsedContent.after(_this.content);
+
         if (typeof gl.diffNotesCompileComponents !== 'undefined') {
           gl.diffNotesCompileComponents();
         }
-      } else {
-        this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right');
-        return this.getContentHTML(cb);
-      }
-    };
-
-    SingleFileDiff.prototype.getContentHTML = function(cb) {
-      this.collapsedContent.hide();
-      this.loadingContent.show();
-      $.get(this.diffForPath, (function(_this) {
-        return function(data) {
-          _this.loadingContent.hide();
-          if (data.html) {
-            _this.content = $(data.html);
-            _this.content.syntaxHighlight();
-          } else {
-            _this.hasError = true;
-            _this.content = $(ERROR_HTML);
-          }
-          _this.collapsedContent.after(_this.content);
 
-          if (typeof gl.diffNotesCompileComponents !== 'undefined') {
-            gl.diffNotesCompileComponents();
-          }
+        FilesCommentButton.init($(_this.file));
 
-          if (cb) cb();
-        };
-      })(this));
-    };
+        if (cb) cb();
+      };
+    })(this));
+  };
 
-    return SingleFileDiff;
-  })();
+  return SingleFileDiff;
+})();
 
-  $.fn.singleFileDiff = function() {
-    return this.each(function() {
-      if (!$.data(this, 'singleFileDiff')) {
-        return $.data(this, 'singleFileDiff', new window.SingleFileDiff(this));
-      }
-    });
-  };
-}).call(window);
+$.fn.singleFileDiff = function() {
+  return this.each(function() {
+    if (!$.data(this, 'singleFileDiff')) {
+      return $.data(this, 'singleFileDiff', new window.SingleFileDiff(this));
+    }
+  });
+};
diff --git a/app/assets/javascripts/smart_interval.js b/app/assets/javascripts/smart_interval.js
index d1bdc353be2d63c20d92a63aee5f9222717348b0..2bf7a3a5d613745d36c55ea21933cdcfbea3b937 100644
--- a/app/assets/javascripts/smart_interval.js
+++ b/app/assets/javascripts/smart_interval.js
@@ -1,158 +1,157 @@
-/*
-* Instances of SmartInterval extend the functionality of `setInterval`, make it configurable
-* and controllable by a public API.
-*
-* */
-
-(() => {
-  class SmartInterval {
-    /**
-      * @param { function } opts.callback Function to be called on each iteration (required)
-      * @param { milliseconds } opts.startingInterval `currentInterval` is set to this initially
-      * @param { milliseconds } opts.maxInterval `currentInterval` will be incremented to this
-      * @param { milliseconds } opts.hiddenInterval `currentInterval` is set to this
-      *                         when the page is hidden
-      * @param { integer } opts.incrementByFactorOf `currentInterval` is incremented by this factor
-      * @param { boolean } opts.lazyStart Configure if timer is initialized on
-      *                    instantiation or lazily
-      * @param { boolean } opts.immediateExecution Configure if callback should
-      *                    be executed before the first interval.
-      */
-    constructor(opts = {}) {
-      this.cfg = {
-        callback: opts.callback,
-        startingInterval: opts.startingInterval,
-        maxInterval: opts.maxInterval,
-        hiddenInterval: opts.hiddenInterval,
-        incrementByFactorOf: opts.incrementByFactorOf,
-        lazyStart: opts.lazyStart,
-        immediateExecution: opts.immediateExecution,
-      };
-
-      this.state = {
-        intervalId: null,
-        currentInterval: this.cfg.startingInterval,
-        pageVisibility: 'visible',
-      };
-
-      this.initInterval();
-    }
-    /* public */
-
-    start() {
-      const cfg = this.cfg;
-      const state = this.state;
-
-      if (cfg.immediateExecution) {
-        cfg.immediateExecution = false;
-        cfg.callback();
-      }
+/**
+ * Instances of SmartInterval extend the functionality of `setInterval`, make it configurable
+ * and controllable by a public API.
+ */
+
+class SmartInterval {
+  /**
+   * @param { function } opts.callback Function to be called on each iteration (required)
+   * @param { milliseconds } opts.startingInterval `currentInterval` is set to this initially
+   * @param { milliseconds } opts.maxInterval `currentInterval` will be incremented to this
+   * @param { milliseconds } opts.hiddenInterval `currentInterval` is set to this
+   *                         when the page is hidden
+   * @param { integer } opts.incrementByFactorOf `currentInterval` is incremented by this factor
+   * @param { boolean } opts.lazyStart Configure if timer is initialized on
+   *                    instantiation or lazily
+   * @param { boolean } opts.immediateExecution Configure if callback should
+   *                    be executed before the first interval.
+   */
+  constructor(opts = {}) {
+    this.cfg = {
+      callback: opts.callback,
+      startingInterval: opts.startingInterval,
+      maxInterval: opts.maxInterval,
+      hiddenInterval: opts.hiddenInterval,
+      incrementByFactorOf: opts.incrementByFactorOf,
+      lazyStart: opts.lazyStart,
+      immediateExecution: opts.immediateExecution,
+    };
+
+    this.state = {
+      intervalId: null,
+      currentInterval: this.cfg.startingInterval,
+      pageVisibility: 'visible',
+    };
+
+    this.initInterval();
+  }
 
-      state.intervalId = window.setInterval(() => {
-        cfg.callback();
+  /* public */
 
-        if (this.getCurrentInterval() === cfg.maxInterval) {
-          return;
-        }
+  start() {
+    const cfg = this.cfg;
+    const state = this.state;
 
-        this.incrementInterval();
-        this.resume();
-      }, this.getCurrentInterval());
+    if (cfg.immediateExecution) {
+      cfg.immediateExecution = false;
+      cfg.callback();
     }
 
-    // cancel the existing timer, setting the currentInterval back to startingInterval
-    cancel() {
-      this.setCurrentInterval(this.cfg.startingInterval);
-      this.stopTimer();
-    }
+    state.intervalId = window.setInterval(() => {
+      cfg.callback();
 
-    onVisibilityHidden() {
-      if (this.cfg.hiddenInterval) {
-        this.setCurrentInterval(this.cfg.hiddenInterval);
-        this.resume();
-      } else {
-        this.cancel();
+      if (this.getCurrentInterval() === cfg.maxInterval) {
+        return;
       }
-    }
 
-    // start a timer, using the existing interval
-    resume() {
-      this.stopTimer(); // stop exsiting timer, in case timer was not previously stopped
-      this.start();
-    }
+      this.incrementInterval();
+      this.resume();
+    }, this.getCurrentInterval());
+  }
 
-    onVisibilityVisible() {
-      this.cancel();
-      this.start();
-    }
+  // cancel the existing timer, setting the currentInterval back to startingInterval
+  cancel() {
+    this.setCurrentInterval(this.cfg.startingInterval);
+    this.stopTimer();
+  }
 
-    destroy() {
+  onVisibilityHidden() {
+    if (this.cfg.hiddenInterval) {
+      this.setCurrentInterval(this.cfg.hiddenInterval);
+      this.resume();
+    } else {
       this.cancel();
-      document.removeEventListener('visibilitychange', this.handleVisibilityChange);
-      $(document).off('visibilitychange').off('beforeunload');
     }
+  }
 
-    /* private */
+  // start a timer, using the existing interval
+  resume() {
+    this.stopTimer(); // stop exsiting timer, in case timer was not previously stopped
+    this.start();
+  }
 
-    initInterval() {
-      const cfg = this.cfg;
+  onVisibilityVisible() {
+    this.cancel();
+    this.start();
+  }
 
-      if (!cfg.lazyStart) {
-        this.start();
-      }
+  destroy() {
+    this.cancel();
+    document.removeEventListener('visibilitychange', this.handleVisibilityChange);
+    $(document).off('visibilitychange').off('beforeunload');
+  }
 
-      this.initVisibilityChangeHandling();
-      this.initPageUnloadHandling();
-    }
+  /* private */
 
-    initVisibilityChangeHandling() {
-      // cancel interval when tab no longer shown (prevents cached pages from polling)
-      document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this));
-    }
+  initInterval() {
+    const cfg = this.cfg;
 
-    initPageUnloadHandling() {
-      // TODO: Consider refactoring in light of turbolinks removal.
-      // prevent interval continuing after page change, when kept in cache by Turbolinks
-      $(document).on('beforeunload', () => this.cancel());
+    if (!cfg.lazyStart) {
+      this.start();
     }
 
-    handleVisibilityChange(e) {
-      this.state.pageVisibility = e.target.visibilityState;
-      const intervalAction = this.isPageVisible() ?
-        this.onVisibilityVisible :
-        this.onVisibilityHidden;
+    this.initVisibilityChangeHandling();
+    this.initPageUnloadHandling();
+  }
 
-      intervalAction.apply(this);
-    }
+  initVisibilityChangeHandling() {
+    // cancel interval when tab no longer shown (prevents cached pages from polling)
+    document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this));
+  }
 
-    getCurrentInterval() {
-      return this.state.currentInterval;
-    }
+  initPageUnloadHandling() {
+    // TODO: Consider refactoring in light of turbolinks removal.
+    // prevent interval continuing after page change, when kept in cache by Turbolinks
+    $(document).on('beforeunload', () => this.cancel());
+  }
 
-    setCurrentInterval(newInterval) {
-      this.state.currentInterval = newInterval;
-    }
+  handleVisibilityChange(e) {
+    this.state.pageVisibility = e.target.visibilityState;
+    const intervalAction = this.isPageVisible() ?
+      this.onVisibilityVisible :
+      this.onVisibilityHidden;
 
-    incrementInterval() {
-      const cfg = this.cfg;
-      const currentInterval = this.getCurrentInterval();
-      if (cfg.hiddenInterval && !this.isPageVisible()) return;
-      let nextInterval = currentInterval * cfg.incrementByFactorOf;
+    intervalAction.apply(this);
+  }
 
-      if (nextInterval > cfg.maxInterval) {
-        nextInterval = cfg.maxInterval;
-      }
+  getCurrentInterval() {
+    return this.state.currentInterval;
+  }
+
+  setCurrentInterval(newInterval) {
+    this.state.currentInterval = newInterval;
+  }
+
+  incrementInterval() {
+    const cfg = this.cfg;
+    const currentInterval = this.getCurrentInterval();
+    if (cfg.hiddenInterval && !this.isPageVisible()) return;
+    let nextInterval = currentInterval * cfg.incrementByFactorOf;
 
-      this.setCurrentInterval(nextInterval);
+    if (nextInterval > cfg.maxInterval) {
+      nextInterval = cfg.maxInterval;
     }
 
-    isPageVisible() { return this.state.pageVisibility === 'visible'; }
+    this.setCurrentInterval(nextInterval);
+  }
 
-    stopTimer() {
-      const state = this.state;
+  isPageVisible() { return this.state.pageVisibility === 'visible'; }
 
-      state.intervalId = window.clearInterval(state.intervalId);
-    }
+  stopTimer() {
+    const state = this.state;
+
+    state.intervalId = window.clearInterval(state.intervalId);
   }
-  gl.SmartInterval = SmartInterval;
-})(window.gl || (window.gl = {}));
+}
+
+window.gl.SmartInterval = SmartInterval;
diff --git a/app/assets/javascripts/snippets_list.js b/app/assets/javascripts/snippets_list.js
index 2128007113f3281079fe53661d1830d453137901..da7b9e08447700c5d66b62526d3fab95933aa757 100644
--- a/app/assets/javascripts/snippets_list.js
+++ b/app/assets/javascripts/snippets_list.js
@@ -1,13 +1,9 @@
 /* eslint-disable arrow-parens, no-param-reassign, space-before-function-paren, func-names, no-var, max-len */
 
-(global => {
-  global.gl = global.gl || {};
+window.gl.SnippetsList = function() {
+  var $holder = $('.snippets-list-holder');
 
-  gl.SnippetsList = function() {
-    var $holder = $('.snippets-list-holder');
-
-    $holder.find('.pagination').on('ajax:success', (e, data) => {
-      $holder.replaceWith(data.html);
-    });
-  };
-})(window);
+  $holder.find('.pagination').on('ajax:success', (e, data) => {
+    $holder.replaceWith(data.html);
+  });
+};
diff --git a/app/assets/javascripts/star.js b/app/assets/javascripts/star.js
index c75b44cc2fdcb840a09f7247b52cdf59605876b1..840ae1edd9d26fafd610fe31e0358b5d8bd7c2bf 100644
--- a/app/assets/javascripts/star.js
+++ b/app/assets/javascripts/star.js
@@ -1,30 +1,28 @@
 /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-unused-vars, one-var, no-var, one-var-declaration-per-line, prefer-arrow-callback, no-new, max-len */
 /* global Flash */
 
-(function() {
-  this.Star = (function() {
-    function Star() {
-      $('.project-home-panel .toggle-star').on('ajax:success', function(e, data, status, xhr) {
-        var $starIcon, $starSpan, $this, toggleStar;
-        $this = $(this);
-        $starSpan = $this.find('span');
-        $starIcon = $this.find('i');
-        toggleStar = function(isStarred) {
-          $this.parent().find('.star-count').text(data.star_count);
-          if (isStarred) {
-            $starSpan.removeClass('starred').text('Star');
-            $starIcon.removeClass('fa-star').addClass('fa-star-o');
-          } else {
-            $starSpan.addClass('starred').text('Unstar');
-            $starIcon.removeClass('fa-star-o').addClass('fa-star');
-          }
-        };
-        toggleStar($starSpan.hasClass('starred'));
-      }).on('ajax:error', function(e, xhr, status, error) {
-        new Flash('Star toggle failed. Try again later.', 'alert');
-      });
-    }
+window.Star = (function() {
+  function Star() {
+    $('.project-home-panel .toggle-star').on('ajax:success', function(e, data, status, xhr) {
+      var $starIcon, $starSpan, $this, toggleStar;
+      $this = $(this);
+      $starSpan = $this.find('span');
+      $starIcon = $this.find('i');
+      toggleStar = function(isStarred) {
+        $this.parent().find('.star-count').text(data.star_count);
+        if (isStarred) {
+          $starSpan.removeClass('starred').text('Star');
+          $starIcon.removeClass('fa-star').addClass('fa-star-o');
+        } else {
+          $starSpan.addClass('starred').text('Unstar');
+          $starIcon.removeClass('fa-star-o').addClass('fa-star');
+        }
+      };
+      toggleStar($starSpan.hasClass('starred'));
+    }).on('ajax:error', function(e, xhr, status, error) {
+      new Flash('Star toggle failed. Try again later.', 'alert');
+    });
+  }
 
-    return Star;
-  })();
-}).call(window);
+  return Star;
+})();
diff --git a/app/assets/javascripts/subscription.js b/app/assets/javascripts/subscription.js
index 5f9a3e00c228fb4d51f527a3e30db0503a140a5d..bb4d68fcd49a45bc1e0fe01ae9686dd408a2009a 100644
--- a/app/assets/javascripts/subscription.js
+++ b/app/assets/javascripts/subscription.js
@@ -1,47 +1,45 @@
-(() => {
-  class Subscription {
-    constructor(containerElm) {
-      this.containerElm = containerElm;
+class Subscription {
+  constructor(containerElm) {
+    this.containerElm = containerElm;
 
-      const subscribeButton = containerElm.querySelector('.js-subscribe-button');
-      if (subscribeButton) {
-        // remove class so we don't bind twice
-        subscribeButton.classList.remove('js-subscribe-button');
-        subscribeButton.addEventListener('click', this.toggleSubscription.bind(this));
-      }
+    const subscribeButton = containerElm.querySelector('.js-subscribe-button');
+    if (subscribeButton) {
+      // remove class so we don't bind twice
+      subscribeButton.classList.remove('js-subscribe-button');
+      subscribeButton.addEventListener('click', this.toggleSubscription.bind(this));
     }
+  }
 
-    toggleSubscription(event) {
-      const button = event.currentTarget;
-      const buttonSpan = button.querySelector('span');
-      if (!buttonSpan || button.classList.contains('disabled')) {
-        return;
-      }
-      button.classList.add('disabled');
+  toggleSubscription(event) {
+    const button = event.currentTarget;
+    const buttonSpan = button.querySelector('span');
+    if (!buttonSpan || button.classList.contains('disabled')) {
+      return;
+    }
+    button.classList.add('disabled');
 
-      const isSubscribed = buttonSpan.innerHTML.trim().toLowerCase() !== 'subscribe';
-      const toggleActionUrl = this.containerElm.dataset.url;
+    const isSubscribed = buttonSpan.innerHTML.trim().toLowerCase() !== 'subscribe';
+    const toggleActionUrl = this.containerElm.dataset.url;
 
-      $.post(toggleActionUrl, () => {
-        button.classList.remove('disabled');
+    $.post(toggleActionUrl, () => {
+      button.classList.remove('disabled');
 
-        // hack to allow this to work with the issue boards Vue object
-        if (document.querySelector('html').classList.contains('issue-boards-page')) {
-          gl.issueBoards.boardStoreIssueSet(
-            'subscribed',
-            !gl.issueBoards.BoardsStore.detail.issue.subscribed,
-          );
-        } else {
-          buttonSpan.innerHTML = isSubscribed ? 'Subscribe' : 'Unsubscribe';
-        }
-      });
-    }
+      // hack to allow this to work with the issue boards Vue object
+      if (document.querySelector('html').classList.contains('issue-boards-page')) {
+        gl.issueBoards.boardStoreIssueSet(
+          'subscribed',
+          !gl.issueBoards.BoardsStore.detail.issue.subscribed,
+        );
+      } else {
+        buttonSpan.innerHTML = isSubscribed ? 'Subscribe' : 'Unsubscribe';
+      }
+    });
+  }
 
-    static bindAll(selector) {
-      [].forEach.call(document.querySelectorAll(selector), elm => new Subscription(elm));
-    }
+  static bindAll(selector) {
+    [].forEach.call(document.querySelectorAll(selector), elm => new Subscription(elm));
   }
+}
 
-  window.gl = window.gl || {};
-  window.gl.Subscription = Subscription;
-})();
+window.gl = window.gl || {};
+window.gl.Subscription = Subscription;
diff --git a/app/assets/javascripts/subscription_select.js b/app/assets/javascripts/subscription_select.js
index 0cd591c73208673730951ea4fccbef19385325fa..a48434181b6f75cc6d1d83701be916450567fe9a 100644
--- a/app/assets/javascripts/subscription_select.js
+++ b/app/assets/javascripts/subscription_select.js
@@ -1,34 +1,33 @@
 /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, object-shorthand, no-unused-vars, no-shadow, one-var, one-var-declaration-per-line, comma-dangle, max-len */
-(function() {
-  this.SubscriptionSelect = (function() {
-    function SubscriptionSelect() {
-      $('.js-subscription-event').each(function(i, el) {
-        var fieldName;
-        fieldName = $(el).data("field-name");
-        return $(el).glDropdown({
-          selectable: true,
-          fieldName: fieldName,
-          toggleLabel: (function(_this) {
-            return function(selected, el, instance) {
-              var $item, label;
-              label = 'Subscription';
-              $item = instance.dropdown.find('.is-active');
-              if ($item.length) {
-                label = $item.text();
-              }
-              return label;
-            };
-          })(this),
-          clicked: function(options) {
-            return options.e.preventDefault();
-          },
-          id: function(obj, el) {
-            return $(el).data("id");
-          }
-        });
+
+window.SubscriptionSelect = (function() {
+  function SubscriptionSelect() {
+    $('.js-subscription-event').each(function(i, el) {
+      var fieldName;
+      fieldName = $(el).data("field-name");
+      return $(el).glDropdown({
+        selectable: true,
+        fieldName: fieldName,
+        toggleLabel: (function(_this) {
+          return function(selected, el, instance) {
+            var $item, label;
+            label = 'Subscription';
+            $item = instance.dropdown.find('.is-active');
+            if ($item.length) {
+              label = $item.text();
+            }
+            return label;
+          };
+        })(this),
+        clicked: function(options) {
+          return options.e.preventDefault();
+        },
+        id: function(obj, el) {
+          return $(el).data("id");
+        }
       });
-    }
+    });
+  }
 
-    return SubscriptionSelect;
-  })();
-}).call(window);
+  return SubscriptionSelect;
+})();
diff --git a/app/assets/javascripts/syntax_highlight.js b/app/assets/javascripts/syntax_highlight.js
index 7c063fae045480771db5d9339ab448ddb2fe4067..662d6b36c16afc422ca0329afe2d6f6f26824c02 100644
--- a/app/assets/javascripts/syntax_highlight.js
+++ b/app/assets/javascripts/syntax_highlight.js
@@ -9,19 +9,18 @@
 //
 //   <div class="js-syntax-highlight"></div>
 //
-(function() {
-  $.fn.syntaxHighlight = function() {
-    var $children;
 
-    if ($(this).hasClass('js-syntax-highlight')) {
-      // Given the element itself, apply highlighting
-      return $(this).addClass(gon.user_color_scheme);
-    } else {
-      // Given a parent element, recurse to any of its applicable children
-      $children = $(this).find('.js-syntax-highlight');
-      if ($children.length) {
-        return $children.syntaxHighlight();
-      }
+$.fn.syntaxHighlight = function() {
+  var $children;
+
+  if ($(this).hasClass('js-syntax-highlight')) {
+    // Given the element itself, apply highlighting
+    return $(this).addClass(gon.user_color_scheme);
+  } else {
+    // Given a parent element, recurse to any of its applicable children
+    $children = $(this).find('.js-syntax-highlight');
+    if ($children.length) {
+      return $children.syntaxHighlight();
     }
-  };
-}).call(window);
+  }
+};
diff --git a/app/assets/javascripts/tree.js b/app/assets/javascripts/tree.js
index 76a821c7a17338ace56a57267c251df791b6512b..77ae6109bc6d21411e995b6b304287b9d36b225e 100644
--- a/app/assets/javascripts/tree.js
+++ b/app/assets/javascripts/tree.js
@@ -1,68 +1,66 @@
 /* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, quotes, consistent-return, no-var, one-var, one-var-declaration-per-line, no-else-return, prefer-arrow-callback, max-len */
 
-(function() {
-  this.TreeView = (function() {
-    function TreeView() {
-      this.initKeyNav();
-      // Code browser tree slider
-      // Make the entire tree-item row clickable, but not if clicking another link (like a commit message)
-      $(".tree-content-holder .tree-item").on('click', function(e) {
-        var $clickedEl, path;
-        $clickedEl = $(e.target);
-        path = $('.tree-item-file-name a', this).attr('href');
-        if (!$clickedEl.is('a') && !$clickedEl.is('.str-truncated')) {
-          if (e.metaKey || e.which === 2) {
-            e.preventDefault();
-            return window.open(path, '_blank');
-          } else {
-            return gl.utils.visitUrl(path);
-          }
+window.TreeView = (function() {
+  function TreeView() {
+    this.initKeyNav();
+    // Code browser tree slider
+    // Make the entire tree-item row clickable, but not if clicking another link (like a commit message)
+    $(".tree-content-holder .tree-item").on('click', function(e) {
+      var $clickedEl, path;
+      $clickedEl = $(e.target);
+      path = $('.tree-item-file-name a', this).attr('href');
+      if (!$clickedEl.is('a') && !$clickedEl.is('.str-truncated')) {
+        if (e.metaKey || e.which === 2) {
+          e.preventDefault();
+          return window.open(path, '_blank');
+        } else {
+          return gl.utils.visitUrl(path);
         }
-      });
-      // Show the "Loading commit data" for only the first element
-      $('span.log_loading:first').removeClass('hide');
-    }
+      }
+    });
+    // Show the "Loading commit data" for only the first element
+    $('span.log_loading:first').removeClass('hide');
+  }
 
-    TreeView.prototype.initKeyNav = function() {
-      var li, liSelected;
-      li = $("tr.tree-item");
-      liSelected = null;
-      return $('body').keydown(function(e) {
-        var next, path;
-        if ($("input:focus").length > 0 && (e.which === 38 || e.which === 40)) {
-          return false;
-        }
-        if (e.which === 40) {
-          if (liSelected) {
-            next = liSelected.next();
-            if (next.length > 0) {
-              liSelected.removeClass("selected");
-              liSelected = next.addClass("selected");
-            }
-          } else {
-            liSelected = li.eq(0).addClass("selected");
-          }
-          return $(liSelected).focus();
-        } else if (e.which === 38) {
-          if (liSelected) {
-            next = liSelected.prev();
-            if (next.length > 0) {
-              liSelected.removeClass("selected");
-              liSelected = next.addClass("selected");
-            }
-          } else {
-            liSelected = li.last().addClass("selected");
+  TreeView.prototype.initKeyNav = function() {
+    var li, liSelected;
+    li = $("tr.tree-item");
+    liSelected = null;
+    return $('body').keydown(function(e) {
+      var next, path;
+      if ($("input:focus").length > 0 && (e.which === 38 || e.which === 40)) {
+        return false;
+      }
+      if (e.which === 40) {
+        if (liSelected) {
+          next = liSelected.next();
+          if (next.length > 0) {
+            liSelected.removeClass("selected");
+            liSelected = next.addClass("selected");
           }
-          return $(liSelected).focus();
-        } else if (e.which === 13) {
-          path = $('.tree-item.selected .tree-item-file-name a').attr('href');
-          if (path) {
-            return gl.utils.visitUrl(path);
+        } else {
+          liSelected = li.eq(0).addClass("selected");
+        }
+        return $(liSelected).focus();
+      } else if (e.which === 38) {
+        if (liSelected) {
+          next = liSelected.prev();
+          if (next.length > 0) {
+            liSelected.removeClass("selected");
+            liSelected = next.addClass("selected");
           }
+        } else {
+          liSelected = li.last().addClass("selected");
+        }
+        return $(liSelected).focus();
+      } else if (e.which === 13) {
+        path = $('.tree-item.selected .tree-item-file-name a').attr('href');
+        if (path) {
+          return gl.utils.visitUrl(path);
         }
-      });
-    };
+      }
+    });
+  };
 
-    return TreeView;
-  })();
-}).call(window);
+  return TreeView;
+})();
diff --git a/app/assets/javascripts/user.js b/app/assets/javascripts/user.js
index 19c9efe7fbda9b8ed3ae2bd10eb3a736d19c3a83..3ab9ef5408e9c153e69690988f0c38563dd28ff3 100644
--- a/app/assets/javascripts/user.js
+++ b/app/assets/javascripts/user.js
@@ -2,34 +2,35 @@
 
 import Cookies from 'js-cookie';
 
-((global) => {
-  global.User = class {
-    constructor({ action }) {
-      this.action = action;
-      this.placeProfileAvatarsToTop();
-      this.initTabs();
-      this.hideProjectLimitMessage();
-    }
+class User {
+  constructor({ action }) {
+    this.action = action;
+    this.placeProfileAvatarsToTop();
+    this.initTabs();
+    this.hideProjectLimitMessage();
+  }
 
-    placeProfileAvatarsToTop() {
-      $('.profile-groups-avatars').tooltip({
-        placement: 'top'
-      });
-    }
+  placeProfileAvatarsToTop() {
+    $('.profile-groups-avatars').tooltip({
+      placement: 'top'
+    });
+  }
 
-    initTabs() {
-      return new global.UserTabs({
-        parentEl: '.user-profile',
-        action: this.action
-      });
-    }
+  initTabs() {
+    return new window.gl.UserTabs({
+      parentEl: '.user-profile',
+      action: this.action
+    });
+  }
 
-    hideProjectLimitMessage() {
-      $('.hide-project-limit-message').on('click', e => {
-        e.preventDefault();
-        Cookies.set('hide_project_limit_message', 'false');
-        $(this).parents('.project-limit-message').remove();
-      });
-    }
-  };
-})(window.gl || (window.gl = {}));
+  hideProjectLimitMessage() {
+    $('.hide-project-limit-message').on('click', e => {
+      e.preventDefault();
+      Cookies.set('hide_project_limit_message', 'false');
+      $(this).parents('.project-limit-message').remove();
+    });
+  }
+}
+
+window.gl = window.gl || {};
+window.gl.User = User;
diff --git a/app/assets/javascripts/user_tabs.js b/app/assets/javascripts/user_tabs.js
index ce7eb76dc714188197062d2d2d2eee53f5a8f1ac..be70f4cb4e2578124d878d267850544817f01ca8 100644
--- a/app/assets/javascripts/user_tabs.js
+++ b/app/assets/javascripts/user_tabs.js
@@ -59,117 +59,118 @@ content on the Users#show page.
      </div>
    </div>
 */
-((global) => {
-  class UserTabs {
-    constructor ({ defaultAction, action, parentEl }) {
-      this.loaded = {};
-      this.defaultAction = defaultAction || 'activity';
-      this.action = action || this.defaultAction;
-      this.$parentEl = $(parentEl) || $(document);
-      this._location = window.location;
-      this.$parentEl.find('.nav-links a')
-        .each((i, navLink) => {
-          this.loaded[$(navLink).attr('data-action')] = false;
-        });
-      this.actions = Object.keys(this.loaded);
-      this.bindEvents();
-
-      if (this.action === 'show') {
-        this.action = this.defaultAction;
-      }
 
-      this.activateTab(this.action);
+class UserTabs {
+  constructor ({ defaultAction, action, parentEl }) {
+    this.loaded = {};
+    this.defaultAction = defaultAction || 'activity';
+    this.action = action || this.defaultAction;
+    this.$parentEl = $(parentEl) || $(document);
+    this._location = window.location;
+    this.$parentEl.find('.nav-links a')
+      .each((i, navLink) => {
+        this.loaded[$(navLink).attr('data-action')] = false;
+      });
+    this.actions = Object.keys(this.loaded);
+    this.bindEvents();
+
+    if (this.action === 'show') {
+      this.action = this.defaultAction;
     }
 
-    bindEvents() {
-      this.changeProjectsPageWrapper = this.changeProjectsPage.bind(this);
+    this.activateTab(this.action);
+  }
 
-      this.$parentEl.off('shown.bs.tab', '.nav-links a[data-toggle="tab"]')
-        .on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', event => this.tabShown(event));
+  bindEvents() {
+    this.changeProjectsPageWrapper = this.changeProjectsPage.bind(this);
 
-      this.$parentEl.on('click', '.gl-pagination a', this.changeProjectsPageWrapper);
-    }
+    this.$parentEl.off('shown.bs.tab', '.nav-links a[data-toggle="tab"]')
+      .on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', event => this.tabShown(event));
 
-    changeProjectsPage(e) {
-      e.preventDefault();
+    this.$parentEl.on('click', '.gl-pagination a', this.changeProjectsPageWrapper);
+  }
 
-      $('.tab-pane.active').empty();
-      const endpoint = $(e.target).attr('href');
-      this.loadTab(this.getCurrentAction(), endpoint);
-    }
+  changeProjectsPage(e) {
+    e.preventDefault();
 
-    tabShown(event) {
-      const $target = $(event.target);
-      const action = $target.data('action');
-      const source = $target.attr('href');
-      const endpoint = $target.data('endpoint');
-      this.setTab(action, endpoint);
-      return this.setCurrentAction(source);
-    }
+    $('.tab-pane.active').empty();
+    const endpoint = $(e.target).attr('href');
+    this.loadTab(this.getCurrentAction(), endpoint);
+  }
 
-    activateTab(action) {
-      return this.$parentEl.find(`.nav-links .js-${action}-tab a`)
-        .tab('show');
-    }
+  tabShown(event) {
+    const $target = $(event.target);
+    const action = $target.data('action');
+    const source = $target.attr('href');
+    const endpoint = $target.data('endpoint');
+    this.setTab(action, endpoint);
+    return this.setCurrentAction(source);
+  }
 
-    setTab(action, endpoint) {
-      if (this.loaded[action]) {
-        return;
-      }
-      if (action === 'activity') {
-        this.loadActivities();
-      }
+  activateTab(action) {
+    return this.$parentEl.find(`.nav-links .js-${action}-tab a`)
+      .tab('show');
+  }
 
-      const loadableActions = ['groups', 'contributed', 'projects', 'snippets'];
-      if (loadableActions.indexOf(action) > -1) {
-        return this.loadTab(action, endpoint);
-      }
+  setTab(action, endpoint) {
+    if (this.loaded[action]) {
+      return;
+    }
+    if (action === 'activity') {
+      this.loadActivities();
     }
 
-    loadTab(action, endpoint) {
-      return $.ajax({
-        beforeSend: () => this.toggleLoading(true),
-        complete: () => this.toggleLoading(false),
-        dataType: 'json',
-        type: 'GET',
-        url: endpoint,
-        success: (data) => {
-          const tabSelector = `div#${action}`;
-          this.$parentEl.find(tabSelector).html(data.html);
-          this.loaded[action] = true;
-          return gl.utils.localTimeAgo($('.js-timeago', tabSelector));
-        }
-      });
+    const loadableActions = ['groups', 'contributed', 'projects', 'snippets'];
+    if (loadableActions.indexOf(action) > -1) {
+      return this.loadTab(action, endpoint);
     }
+  }
 
-    loadActivities() {
-      if (this.loaded['activity']) {
-        return;
+  loadTab(action, endpoint) {
+    return $.ajax({
+      beforeSend: () => this.toggleLoading(true),
+      complete: () => this.toggleLoading(false),
+      dataType: 'json',
+      type: 'GET',
+      url: endpoint,
+      success: (data) => {
+        const tabSelector = `div#${action}`;
+        this.$parentEl.find(tabSelector).html(data.html);
+        this.loaded[action] = true;
+        return gl.utils.localTimeAgo($('.js-timeago', tabSelector));
       }
-      const $calendarWrap = this.$parentEl.find('.user-calendar');
-      $calendarWrap.load($calendarWrap.data('href'));
-      new gl.Activities();
-      return this.loaded['activity'] = true;
-    }
+    });
+  }
 
-    toggleLoading(status) {
-      return this.$parentEl.find('.loading-status .loading')
-        .toggle(status);
+  loadActivities() {
+    if (this.loaded['activity']) {
+      return;
     }
+    const $calendarWrap = this.$parentEl.find('.user-calendar');
+    $calendarWrap.load($calendarWrap.data('href'));
+    new gl.Activities();
+    return this.loaded['activity'] = true;
+  }
 
-    setCurrentAction(source) {
-      let new_state = source;
-      new_state = new_state.replace(/\/+$/, '');
-      new_state += this._location.search + this._location.hash;
-      history.replaceState({
-        url: new_state
-      }, document.title, new_state);
-      return new_state;
-    }
+  toggleLoading(status) {
+    return this.$parentEl.find('.loading-status .loading')
+      .toggle(status);
+  }
 
-    getCurrentAction() {
-      return this.$parentEl.find('.nav-links .active a').data('action');
-    }
+  setCurrentAction(source) {
+    let new_state = source;
+    new_state = new_state.replace(/\/+$/, '');
+    new_state += this._location.search + this._location.hash;
+    history.replaceState({
+      url: new_state
+    }, document.title, new_state);
+    return new_state;
   }
-  global.UserTabs = UserTabs;
-})(window.gl || (window.gl = {}));
+
+  getCurrentAction() {
+    return this.$parentEl.find('.nav-links .active a').data('action');
+  }
+}
+
+window.gl = window.gl || {};
+window.gl.UserTabs = UserTabs;
diff --git a/app/assets/javascripts/username_validator.js b/app/assets/javascripts/username_validator.js
index 137cefa3b8e872d77106ca4159cd12970ce73205..abe6c30f4f3bcb53611d62b94bd7dc0f7d989c52 100644
--- a/app/assets/javascripts/username_validator.js
+++ b/app/assets/javascripts/username_validator.js
@@ -1,135 +1,133 @@
 /* eslint-disable comma-dangle, consistent-return, class-methods-use-this, arrow-parens, no-param-reassign, max-len */
 
-((global) => {
-  const debounceTimeoutDuration = 1000;
-  const invalidInputClass = 'gl-field-error-outline';
-  const successInputClass = 'gl-field-success-outline';
-  const unavailableMessageSelector = '.username .validation-error';
-  const successMessageSelector = '.username .validation-success';
-  const pendingMessageSelector = '.username .validation-pending';
-  const invalidMessageSelector = '.username .gl-field-error';
-
-  class UsernameValidator {
-    constructor() {
-      this.inputElement = $('#new_user_username');
-      this.inputDomElement = this.inputElement.get(0);
-      this.state = {
-        available: false,
-        valid: false,
-        pending: false,
-        empty: true
-      };
-
-      const debounceTimeout = _.debounce((username) => {
-        this.validateUsername(username);
-      }, debounceTimeoutDuration);
-
-      this.inputElement.on('keyup.username_check', () => {
-        const username = this.inputElement.val();
-
-        this.state.valid = this.inputDomElement.validity.valid;
-        this.state.empty = !username.length;
-
-        if (this.state.valid) {
-          return debounceTimeout(username);
-        }
-
-        this.renderState();
-      });
-
-      // Override generic field validation
-      this.inputElement.on('invalid', this.interceptInvalid.bind(this));
-    }
+const debounceTimeoutDuration = 1000;
+const invalidInputClass = 'gl-field-error-outline';
+const successInputClass = 'gl-field-success-outline';
+const unavailableMessageSelector = '.username .validation-error';
+const successMessageSelector = '.username .validation-success';
+const pendingMessageSelector = '.username .validation-pending';
+const invalidMessageSelector = '.username .gl-field-error';
+
+class UsernameValidator {
+  constructor() {
+    this.inputElement = $('#new_user_username');
+    this.inputDomElement = this.inputElement.get(0);
+    this.state = {
+      available: false,
+      valid: false,
+      pending: false,
+      empty: true
+    };
+
+    const debounceTimeout = _.debounce((username) => {
+      this.validateUsername(username);
+    }, debounceTimeoutDuration);
+
+    this.inputElement.on('keyup.username_check', () => {
+      const username = this.inputElement.val();
+
+      this.state.valid = this.inputDomElement.validity.valid;
+      this.state.empty = !username.length;
 
-    renderState() {
-      // Clear all state
-      this.clearFieldValidationState();
-
-      if (this.state.valid && this.state.available) {
-        return this.setSuccessState();
+      if (this.state.valid) {
+        return debounceTimeout(username);
       }
 
-      if (this.state.empty) {
-        return this.clearFieldValidationState();
-      }
+      this.renderState();
+    });
 
-      if (this.state.pending) {
-        return this.setPendingState();
-      }
+    // Override generic field validation
+    this.inputElement.on('invalid', this.interceptInvalid.bind(this));
+  }
 
-      if (!this.state.available) {
-        return this.setUnavailableState();
-      }
+  renderState() {
+    // Clear all state
+    this.clearFieldValidationState();
 
-      if (!this.state.valid) {
-        return this.setInvalidState();
-      }
+    if (this.state.valid && this.state.available) {
+      return this.setSuccessState();
     }
 
-    interceptInvalid(event) {
-      event.preventDefault();
-      event.stopPropagation();
+    if (this.state.empty) {
+      return this.clearFieldValidationState();
     }
 
-    validateUsername(username) {
-      if (this.state.valid) {
-        this.state.pending = true;
-        this.state.available = false;
-        this.renderState();
-        return $.ajax({
-          type: 'GET',
-          url: `${gon.relative_url_root}/users/${username}/exists`,
-          dataType: 'json',
-          success: (res) => this.setAvailabilityState(res.exists)
-        });
-      }
+    if (this.state.pending) {
+      return this.setPendingState();
     }
 
-    setAvailabilityState(usernameTaken) {
-      if (usernameTaken) {
-        this.state.valid = false;
-        this.state.available = false;
-      } else {
-        this.state.available = true;
-      }
-      this.state.pending = false;
-      this.renderState();
+    if (!this.state.available) {
+      return this.setUnavailableState();
     }
 
-    clearFieldValidationState() {
-      this.inputElement.siblings('p').hide();
-
-      this.inputElement.removeClass(invalidInputClass)
-        .removeClass(successInputClass);
+    if (!this.state.valid) {
+      return this.setInvalidState();
     }
+  }
 
-    setUnavailableState() {
-      const $usernameUnavailableMessage = this.inputElement.siblings(unavailableMessageSelector);
-      this.inputElement.addClass(invalidInputClass).removeClass(successInputClass);
-      $usernameUnavailableMessage.show();
-    }
+  interceptInvalid(event) {
+    event.preventDefault();
+    event.stopPropagation();
+  }
 
-    setSuccessState() {
-      const $usernameSuccessMessage = this.inputElement.siblings(successMessageSelector);
-      this.inputElement.addClass(successInputClass).removeClass(invalidInputClass);
-      $usernameSuccessMessage.show();
+  validateUsername(username) {
+    if (this.state.valid) {
+      this.state.pending = true;
+      this.state.available = false;
+      this.renderState();
+      return $.ajax({
+        type: 'GET',
+        url: `${gon.relative_url_root}/users/${username}/exists`,
+        dataType: 'json',
+        success: (res) => this.setAvailabilityState(res.exists)
+      });
     }
+  }
 
-    setPendingState() {
-      const $usernamePendingMessage = $(pendingMessageSelector);
-      if (this.state.pending) {
-        $usernamePendingMessage.show();
-      } else {
-        $usernamePendingMessage.hide();
-      }
+  setAvailabilityState(usernameTaken) {
+    if (usernameTaken) {
+      this.state.valid = false;
+      this.state.available = false;
+    } else {
+      this.state.available = true;
     }
+    this.state.pending = false;
+    this.renderState();
+  }
+
+  clearFieldValidationState() {
+    this.inputElement.siblings('p').hide();
 
-    setInvalidState() {
-      const $inputErrorMessage = $(invalidMessageSelector);
-      this.inputElement.addClass(invalidInputClass).removeClass(successInputClass);
-      $inputErrorMessage.show();
+    this.inputElement.removeClass(invalidInputClass)
+      .removeClass(successInputClass);
+  }
+
+  setUnavailableState() {
+    const $usernameUnavailableMessage = this.inputElement.siblings(unavailableMessageSelector);
+    this.inputElement.addClass(invalidInputClass).removeClass(successInputClass);
+    $usernameUnavailableMessage.show();
+  }
+
+  setSuccessState() {
+    const $usernameSuccessMessage = this.inputElement.siblings(successMessageSelector);
+    this.inputElement.addClass(successInputClass).removeClass(invalidInputClass);
+    $usernameSuccessMessage.show();
+  }
+
+  setPendingState() {
+    const $usernamePendingMessage = $(pendingMessageSelector);
+    if (this.state.pending) {
+      $usernamePendingMessage.show();
+    } else {
+      $usernamePendingMessage.hide();
     }
   }
 
-  global.UsernameValidator = UsernameValidator;
-})(window);
+  setInvalidState() {
+    const $inputErrorMessage = $(invalidMessageSelector);
+    this.inputElement.addClass(invalidInputClass).removeClass(successInputClass);
+    $inputErrorMessage.show();
+  }
+}
+
+window.UsernameValidator = UsernameValidator;
diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js
index ec45253e50bddce19c20d0c0d30232ad86c267e9..5728afb4c5987c27d29dfa6088348a8ae91fd534 100644
--- a/app/assets/javascripts/users_select.js
+++ b/app/assets/javascripts/users_select.js
@@ -206,8 +206,6 @@ function UsersSelect(currentUser, els) {
       return $dropdown.glDropdown({
         showMenuAbove: showMenuAbove,
         data: function(term, callback) {
-          var isAuthorFilter;
-          isAuthorFilter = $('.js-author-search');
           return _this.users(term, options, function(users) {
             // GitLabDropdownFilter returns this.instance
             // GitLabDropdownRemote returns this.options.instance
@@ -643,7 +641,7 @@ UsersSelect.prototype.formatResult = function(user) {
   } else {
     avatar = gon.default_avatar_url;
   }
-  return "<div class='user-result " + (!user.username ? 'no-username' : void 0) + "'> <div class='user-image'><img class='avatar s24' src='" + avatar + "'></div> <div class='user-name'>" + user.name + "</div> <div class='user-username'>" + (user.username || "") + "</div> </div>";
+  return "<div class='user-result " + (!user.username ? 'no-username' : void 0) + "'> <div class='user-image'><img class='avatar avatar-inline s32' src='" + avatar + "'></div> <div class='user-name dropdown-menu-user-full-name'>" + user.name + "</div> <div class='user-username dropdown-menu-user-username'>" + ("@" + user.username || "") + "</div> </div>";
 };
 
 UsersSelect.prototype.formatSelection = function(user) {
diff --git a/app/assets/javascripts/visibility_select.js b/app/assets/javascripts/visibility_select.js
index f712d7ba930c9a04beb324187ff9ffedde4d73a8..b6bbbaa093686a1e98c345fd178c2817422b19c5 100644
--- a/app/assets/javascripts/visibility_select.js
+++ b/app/assets/javascripts/visibility_select.js
@@ -1,27 +1,24 @@
-(() => {
-  const gl = window.gl || (window.gl = {});
-
-  class VisibilitySelect {
-    constructor(container) {
-      if (!container) throw new Error('VisibilitySelect requires a container element as argument 1');
-      this.container = container;
-      this.helpBlock = this.container.querySelector('.help-block');
-      this.select = this.container.querySelector('select');
-    }
+class VisibilitySelect {
+  constructor(container) {
+    if (!container) throw new Error('VisibilitySelect requires a container element as argument 1');
+    this.container = container;
+    this.helpBlock = this.container.querySelector('.help-block');
+    this.select = this.container.querySelector('select');
+  }
 
-    init() {
-      if (this.select) {
-        this.updateHelpText();
-        this.select.addEventListener('change', this.updateHelpText.bind(this));
-      } else {
-        this.helpBlock.textContent = this.container.querySelector('.js-locked').dataset.helpBlock;
-      }
+  init() {
+    if (this.select) {
+      this.updateHelpText();
+      this.select.addEventListener('change', this.updateHelpText.bind(this));
+    } else {
+      this.helpBlock.textContent = this.container.querySelector('.js-locked').dataset.helpBlock;
     }
+  }
 
-    updateHelpText() {
-      this.helpBlock.textContent = this.select.querySelector('option:checked').dataset.description;
-    }
+  updateHelpText() {
+    this.helpBlock.textContent = this.select.querySelector('option:checked').dataset.description;
   }
+}
 
-  gl.VisibilitySelect = VisibilitySelect;
-})();
+window.gl = window.gl || {};
+window.gl.VisibilitySelect = VisibilitySelect;
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js
index 8155218681c8e6366083f2d0003e0e3dc3c0d544..76cb71b6c12d9de538c56e81bb7266233a122d7d 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js
@@ -1,5 +1,5 @@
-import statusCodes from '~/lib/utils/http_status';
-import { bytesToMiB } from '~/lib/utils/number_utils';
+import statusCodes from '../../lib/utils/http_status';
+import { bytesToMiB } from '../../lib/utils/number_utils';
 
 import MemoryGraph from '../../vue_shared/components/memory_graph';
 import MRWidgetService from '../services/mr_widget_service';
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js
index c02e10128e2bb22c810b891ed8feaf7bb08e8f96..e8b3cf2f7292fd3c0bf521179ac46944bda6a687 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js
@@ -17,6 +17,9 @@ export default {
 
       return hasCI && !ciStatus;
     },
+    hasPipeline() {
+      return Object.keys(this.mr.pipeline || {}).length > 0;
+    },
     svg() {
       return statusIconEntityMap.icon_status_failed;
     },
@@ -30,7 +33,11 @@ export default {
   template: `
     <div class="mr-widget-heading">
       <div class="ci-widget">
-        <template v-if="hasCIError">
+        <template v-if="!hasPipeline">
+          <i class="fa fa-spinner fa-spin append-right-10" aria-hidden="true"></i>
+          Waiting for pipeline...
+        </template>
+        <template v-else-if="hasCIError">
           <div class="ci-status-icon ci-status-icon-failed ci-error js-ci-error">
             <span class="js-icon-link icon-link">
               <span
diff --git a/app/assets/javascripts/vue_shared/components/header_ci_component.vue b/app/assets/javascripts/vue_shared/components/header_ci_component.vue
index 1d4d90f75b688ca9e061beceacaea23d1f285d55..bdc059f4a03c9d0b44d04ad6ed5fc2fd74396b83 100644
--- a/app/assets/javascripts/vue_shared/components/header_ci_component.vue
+++ b/app/assets/javascripts/vue_shared/components/header_ci_component.vue
@@ -2,7 +2,7 @@
 import ciIconBadge from './ci_badge_link.vue';
 import loadingIcon from './loading_icon.vue';
 import timeagoTooltip from './time_ago_tooltip.vue';
-import tooltipMixin from '../mixins/tooltip';
+import tooltip from '../directives/tooltip';
 import userAvatarImage from './user_avatar/user_avatar_image.vue';
 
 /**
@@ -47,9 +47,9 @@ export default {
     },
   },
 
-  mixins: [
-    tooltipMixin,
-  ],
+  directives: {
+    tooltip,
+  },
 
   components: {
     ciIconBadge,
@@ -90,10 +90,10 @@ export default {
 
       <template v-if="user">
         <a
+          v-tooltip
           :href="user.path"
           :title="user.email"
-          class="js-user-link commit-committer-link"
-          ref="tooltip">
+          class="js-user-link commit-committer-link">
 
           <user-avatar-image
             :img-src="user.avatar_url"
diff --git a/app/assets/javascripts/vue_shared/components/loading_icon.vue b/app/assets/javascripts/vue_shared/components/loading_icon.vue
index 41b1d0165b0871b6fe259de1cdc38a3594a4f3e4..15581d5c2a05fa981f94d3a108f4eb92355ec73a 100644
--- a/app/assets/javascripts/vue_shared/components/loading_icon.vue
+++ b/app/assets/javascripts/vue_shared/components/loading_icon.vue
@@ -12,9 +12,18 @@
         required: false,
         default: '1',
       },
+
+      inline: {
+        type: Boolean,
+        required: false,
+        default: false,
+      },
     },
 
     computed: {
+      rootElementType() {
+        return this.inline ? 'span' : 'div';
+      },
       cssClass() {
         return `fa-${this.size}x`;
       },
@@ -22,12 +31,14 @@
   };
 </script>
 <template>
-  <div class="text-center">
+  <component
+    :is="this.rootElementType"
+    class="text-center">
     <i
       class="fa fa-spin fa-spinner"
       :class="cssClass"
       aria-hidden="true"
       :aria-label="label">
     </i>
-  </div>
+  </component>
 </template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue
index e6977681e96e052179f6f34ef35f9bfdd6ab3b99..8303c556f6451a4d4fcc8ef54174246d5bd7fa6d 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/field.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue
@@ -64,6 +64,12 @@
       */
       return new gl.GLForm($(this.$refs['gl-form']), true);
     },
+    beforeDestroy() {
+      const glForm = $(this.$refs['gl-form']).data('gl-form');
+      if (glForm) {
+        glForm.destroy();
+      }
+    },
   };
 </script>
 
diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue
index 1a11f493b7fe8c5c8bacec6084249b3af0aa64ae..5bf2a90cc3ba985f095baff3d7abf145bb18ca0c 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue
@@ -1,17 +1,17 @@
 <script>
-  import tooltipMixin from '../../mixins/tooltip';
+  import tooltip from '../../directives/tooltip';
   import toolbarButton from './toolbar_button.vue';
 
   export default {
-    mixins: [
-      tooltipMixin,
-    ],
     props: {
       previewMarkdown: {
         type: Boolean,
         required: true,
       },
     },
+    directives: {
+      tooltip,
+    },
     components: {
       toolbarButton,
     },
@@ -94,13 +94,13 @@
         </div>
         <div class="toolbar-group">
           <button
+            v-tooltip
             aria-label="Go full screen"
             class="toolbar-btn js-zen-enter"
             data-container="body"
             tabindex="-1"
             title="Go full screen"
-            type="button"
-            ref="tooltip">
+            type="button">
             <i
               aria-hidden="true"
               class="fa fa-arrows-alt fa-fw">
diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue
index 096be50762572c1c265bdc8fb727126a96e06872..f7da7ebfcfe07737901d16add34dbe4377565a87 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue
@@ -1,10 +1,7 @@
 <script>
-  import tooltipMixin from '../../mixins/tooltip';
+  import tooltip from '../../directives/tooltip';
 
   export default {
-    mixins: [
-      tooltipMixin,
-    ],
     props: {
       buttonTitle: {
         type: String,
@@ -29,6 +26,9 @@
         default: false,
       },
     },
+    directives: {
+      tooltip,
+    },
     computed: {
       iconClass() {
         return `fa-${this.icon}`;
@@ -39,10 +39,10 @@
 
 <template>
   <button
+    v-tooltip
     type="button"
     class="toolbar-btn js-md hidden-xs"
     tabindex="-1"
-    ref="tooltip"
     data-container="body"
     :data-md-tag="tag"
     :data-md-block="tagBlock"
diff --git a/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue b/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue
index 1c6ef071a6d2d3bc0a5df213e97708fd4c2e7248..3ff7f6e2c4e63740e38f9a1992982243c858892c 100644
--- a/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue
+++ b/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue
@@ -1,5 +1,5 @@
 <script>
-import tooltipMixin from '../mixins/tooltip';
+import tooltip from '../directives/tooltip';
 import timeagoMixin from '../mixins/timeago';
 import '../../lib/utils/datetime_utility';
 
@@ -28,19 +28,21 @@ export default {
   },
 
   mixins: [
-    tooltipMixin,
     timeagoMixin,
   ],
+
+  directives: {
+    tooltip,
+  },
 };
 </script>
 <template>
   <time
+    v-tooltip
     :class="cssClass"
-    class="js-vue-timeago"
     :title="tooltipTitle(time)"
     :data-placement="tooltipPlacement"
-    data-container="body"
-    ref="tooltip">
+    data-container="body">
     {{timeFormated(time)}}
   </time>
 </template>
diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue
index cd6f8c7aee46148df0a6f479ba76b43dde10fb41..dd9a2ebb184f1904e6e1164fce4133ff6688b5f7 100644
--- a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue
+++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue
@@ -16,11 +16,10 @@
 */
 
 import defaultAvatarUrl from 'images/no_avatar.png';
-import TooltipMixin from '../../mixins/tooltip';
+import tooltip from '../../directives/tooltip';
 
 export default {
   name: 'UserAvatarImage',
-  mixins: [TooltipMixin],
   props: {
     imgSrc: {
       type: String,
@@ -53,6 +52,9 @@ export default {
       default: 'top',
     },
   },
+  directives: {
+    tooltip,
+  },
   computed: {
     tooltipContainer() {
       return this.tooltipText ? 'body' : null;
@@ -72,6 +74,7 @@ export default {
 
 <template>
   <img
+    v-tooltip
     class="avatar"
     :class="[avatarSizeClass, cssClasses]"
     :src="imageSource"
@@ -81,6 +84,5 @@ export default {
     :data-container="tooltipContainer"
     :data-placement="tooltipPlacement"
     :title="tooltipText"
-    ref="tooltip"
   />
 </template>
diff --git a/app/assets/javascripts/vue_shared/directives/tooltip.js b/app/assets/javascripts/vue_shared/directives/tooltip.js
new file mode 100644
index 0000000000000000000000000000000000000000..dc896cf5c7ddff2dfff43e08e7d20c9ac80274df
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/directives/tooltip.js
@@ -0,0 +1,13 @@
+export default {
+  bind(el) {
+    $(el).tooltip();
+  },
+
+  componentUpdated(el) {
+    $(el).tooltip('fixTitle');
+  },
+
+  unbind(el) {
+    $(el).tooltip('destroy');
+  },
+};
diff --git a/app/assets/javascripts/vue_shared/mixins/tooltip.js b/app/assets/javascripts/vue_shared/mixins/tooltip.js
deleted file mode 100644
index 995c0c985051c15128c7835eb5de25315d0c2ca0..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/vue_shared/mixins/tooltip.js
+++ /dev/null
@@ -1,13 +0,0 @@
-export default {
-  mounted() {
-    $(this.$refs.tooltip).tooltip();
-  },
-
-  updated() {
-    $(this.$refs.tooltip).tooltip('fixTitle');
-  },
-
-  beforeDestroy() {
-    $(this.$refs.tooltip).tooltip('destroy');
-  },
-};
diff --git a/app/assets/javascripts/webpack.js b/app/assets/javascripts/webpack.js
new file mode 100644
index 0000000000000000000000000000000000000000..9a9cf395fb850977ade734f666390de26ce046bb
--- /dev/null
+++ b/app/assets/javascripts/webpack.js
@@ -0,0 +1,9 @@
+/**
+ * This is the first script loaded by webpack's runtime. It is used to manually configure
+ * config.output.publicPath to account for relative_url_root or CDN settings which cannot be
+ * baked-in to our webpack bundles.
+ */
+
+if (gon && gon.webpack_public_path) {
+  __webpack_public_path__ = gon.webpack_public_path; // eslint-disable-line
+}
diff --git a/app/assets/javascripts/wikis.js b/app/assets/javascripts/wikis.js
index 4194c1bc08de99bf9c3751929b9a5fa7cf0d941f..03d183ebd84da4972f665409a2a9d4ad2d51ae70 100644
--- a/app/assets/javascripts/wikis.js
+++ b/app/assets/javascripts/wikis.js
@@ -4,66 +4,65 @@
 import 'vendor/jquery.nicescroll';
 import './breakpoints';
 
-((global) => {
-  class Wikis {
-    constructor() {
-      this.bp = Breakpoints.get();
-      this.sidebarEl = document.querySelector('.js-wiki-sidebar');
-      this.sidebarExpanded = false;
-      $(this.sidebarEl).niceScroll();
+class Wikis {
+  constructor() {
+    this.bp = Breakpoints.get();
+    this.sidebarEl = document.querySelector('.js-wiki-sidebar');
+    this.sidebarExpanded = false;
+    $(this.sidebarEl).niceScroll();
 
-      const sidebarToggles = document.querySelectorAll('.js-sidebar-wiki-toggle');
-      for (let i = 0; i < sidebarToggles.length; i += 1) {
-        sidebarToggles[i].addEventListener('click', e => this.handleToggleSidebar(e));
-      }
-
-      this.newWikiForm = document.querySelector('form.new-wiki-page');
-      if (this.newWikiForm) {
-        this.newWikiForm.addEventListener('submit', e => this.handleNewWikiSubmit(e));
-      }
+    const sidebarToggles = document.querySelectorAll('.js-sidebar-wiki-toggle');
+    for (let i = 0; i < sidebarToggles.length; i += 1) {
+      sidebarToggles[i].addEventListener('click', e => this.handleToggleSidebar(e));
+    }
 
-      window.addEventListener('resize', () => this.renderSidebar());
-      this.renderSidebar();
+    this.newWikiForm = document.querySelector('form.new-wiki-page');
+    if (this.newWikiForm) {
+      this.newWikiForm.addEventListener('submit', e => this.handleNewWikiSubmit(e));
     }
 
-    handleNewWikiSubmit(e) {
-      if (!this.newWikiForm) return;
+    window.addEventListener('resize', () => this.renderSidebar());
+    this.renderSidebar();
+  }
 
-      const slugInput = this.newWikiForm.querySelector('#new_wiki_path');
-      const slug = gl.text.slugify(slugInput.value);
+  handleNewWikiSubmit(e) {
+    if (!this.newWikiForm) return;
 
-      if (slug.length > 0) {
-        const wikisPath = slugInput.getAttribute('data-wikis-path');
-        window.location.href = `${wikisPath}/${slug}`;
-        e.preventDefault();
-      }
-    }
+    const slugInput = this.newWikiForm.querySelector('#new_wiki_path');
+    const slug = gl.text.slugify(slugInput.value);
 
-    handleToggleSidebar(e) {
+    if (slug.length > 0) {
+      const wikisPath = slugInput.getAttribute('data-wikis-path');
+      window.location.href = `${wikisPath}/${slug}`;
       e.preventDefault();
-      this.sidebarExpanded = !this.sidebarExpanded;
-      this.renderSidebar();
     }
+  }
 
-    sidebarCanCollapse() {
-      const bootstrapBreakpoint = this.bp.getBreakpointSize();
-      return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
-    }
+  handleToggleSidebar(e) {
+    e.preventDefault();
+    this.sidebarExpanded = !this.sidebarExpanded;
+    this.renderSidebar();
+  }
+
+  sidebarCanCollapse() {
+    const bootstrapBreakpoint = this.bp.getBreakpointSize();
+    return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
+  }
 
-    renderSidebar() {
-      if (!this.sidebarEl) return;
-      const { classList } = this.sidebarEl;
-      if (this.sidebarExpanded || !this.sidebarCanCollapse()) {
-        if (!classList.contains('right-sidebar-expanded')) {
-          classList.remove('right-sidebar-collapsed');
-          classList.add('right-sidebar-expanded');
-        }
-      } else if (classList.contains('right-sidebar-expanded')) {
-        classList.add('right-sidebar-collapsed');
-        classList.remove('right-sidebar-expanded');
+  renderSidebar() {
+    if (!this.sidebarEl) return;
+    const { classList } = this.sidebarEl;
+    if (this.sidebarExpanded || !this.sidebarCanCollapse()) {
+      if (!classList.contains('right-sidebar-expanded')) {
+        classList.remove('right-sidebar-collapsed');
+        classList.add('right-sidebar-expanded');
       }
+    } else if (classList.contains('right-sidebar-expanded')) {
+      classList.add('right-sidebar-collapsed');
+      classList.remove('right-sidebar-expanded');
     }
   }
+}
 
-  global.Wikis = Wikis;
-})(window.gl || (window.gl = {}));
+window.gl = window.gl || {};
+window.gl.Wikis = Wikis;
diff --git a/app/assets/javascripts/zen_mode.js b/app/assets/javascripts/zen_mode.js
index b7fe552dec2615aa70435d016285cfddc8150c49..08f80735e936e8ac44edb4aef3d98e653588a391 100644
--- a/app/assets/javascripts/zen_mode.js
+++ b/app/assets/javascripts/zen_mode.js
@@ -34,65 +34,64 @@ window.Dropzone = Dropzone;
 // **Cancelable** No
 // **Target** a.js-zen-leave
 //
-(function() {
-  this.ZenMode = (function() {
-    function ZenMode() {
-      this.active_backdrop = null;
-      this.active_textarea = null;
-      $(document).on('click', '.js-zen-enter', function(e) {
-        e.preventDefault();
-        return $(e.currentTarget).trigger('zen_mode:enter');
-      });
-      $(document).on('click', '.js-zen-leave', function(e) {
+
+window.ZenMode = (function() {
+  function ZenMode() {
+    this.active_backdrop = null;
+    this.active_textarea = null;
+    $(document).on('click', '.js-zen-enter', function(e) {
+      e.preventDefault();
+      return $(e.currentTarget).trigger('zen_mode:enter');
+    });
+    $(document).on('click', '.js-zen-leave', function(e) {
+      e.preventDefault();
+      return $(e.currentTarget).trigger('zen_mode:leave');
+    });
+    $(document).on('zen_mode:enter', (function(_this) {
+      return function(e) {
+        return _this.enter($(e.target).closest('.md-area').find('.zen-backdrop'));
+      };
+    })(this));
+    $(document).on('zen_mode:leave', (function(_this) {
+      return function(e) {
+        return _this.exit();
+      };
+    })(this));
+    $(document).on('keydown', function(e) {
+      // Esc
+      if (e.keyCode === 27) {
         e.preventDefault();
-        return $(e.currentTarget).trigger('zen_mode:leave');
-      });
-      $(document).on('zen_mode:enter', (function(_this) {
-        return function(e) {
-          return _this.enter($(e.target).closest('.md-area').find('.zen-backdrop'));
-        };
-      })(this));
-      $(document).on('zen_mode:leave', (function(_this) {
-        return function(e) {
-          return _this.exit();
-        };
-      })(this));
-      $(document).on('keydown', function(e) {
-        // Esc
-        if (e.keyCode === 27) {
-          e.preventDefault();
-          return $(document).trigger('zen_mode:leave');
-        }
-      });
-    }
+        return $(document).trigger('zen_mode:leave');
+      }
+    });
+  }
 
-    ZenMode.prototype.enter = function(backdrop) {
-      Mousetrap.pause();
-      this.active_backdrop = $(backdrop);
-      this.active_backdrop.addClass('fullscreen');
-      this.active_textarea = this.active_backdrop.find('textarea');
-      // Prevent a user-resized textarea from persisting to fullscreen
-      this.active_textarea.removeAttr('style');
-      return this.active_textarea.focus();
-    };
+  ZenMode.prototype.enter = function(backdrop) {
+    Mousetrap.pause();
+    this.active_backdrop = $(backdrop);
+    this.active_backdrop.addClass('fullscreen');
+    this.active_textarea = this.active_backdrop.find('textarea');
+    // Prevent a user-resized textarea from persisting to fullscreen
+    this.active_textarea.removeAttr('style');
+    return this.active_textarea.focus();
+  };
 
-    ZenMode.prototype.exit = function() {
-      if (this.active_textarea) {
-        Mousetrap.unpause();
-        this.active_textarea.closest('.zen-backdrop').removeClass('fullscreen');
-        this.scrollTo(this.active_textarea);
-        this.active_textarea = null;
-        this.active_backdrop = null;
-        return Dropzone.forElement('.div-dropzone').enable();
-      }
-    };
+  ZenMode.prototype.exit = function() {
+    if (this.active_textarea) {
+      Mousetrap.unpause();
+      this.active_textarea.closest('.zen-backdrop').removeClass('fullscreen');
+      this.scrollTo(this.active_textarea);
+      this.active_textarea = null;
+      this.active_backdrop = null;
+      return Dropzone.forElement('.div-dropzone').enable();
+    }
+  };
 
-    ZenMode.prototype.scrollTo = function(zen_area) {
-      return $.scrollTo(zen_area, 0, {
-        offset: -150
-      });
-    };
+  ZenMode.prototype.scrollTo = function(zen_area) {
+    return $.scrollTo(zen_area, 0, {
+      offset: -150
+    });
+  };
 
-    return ZenMode;
-  })();
-}).call(window);
+  return ZenMode;
+})();
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index fefe5575d9bec1b77bb639006c8debfd67833875..95a08c960eaa9a47b0c97059f718ece481231c78 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -254,7 +254,7 @@
 }
 
 .landing {
-  margin-bottom: $gl-padding;
+  margin: $gl-padding auto;
   overflow: hidden;
   display: flex;
   position: relative;
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index cba890ce8317da6860a6a86317609f30a8f6a370..4f54ca24940000b113e391dff658e815128f85d7 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -395,6 +395,11 @@
 .dropdown-menu-align-right {
   left: auto;
   right: 0;
+  margin-top: -5px;
+
+  @media (max-width: $screen-xs-max) {
+    left: 0;
+  }
 }
 
 .dropdown-menu-selectable {
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index b26d8fbd5fe3d172cd30668a15913052787b9e25..c7c2684d548524ae1ebb78e1d3fd37ed72b9821d 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -12,6 +12,14 @@
 
   &.readme-holder {
     margin: $gl-padding 0;
+
+    &.limited-width-container .file-content {
+      max-width: $limited-layout-width-sm;
+      margin-left: auto;
+      margin-right: auto;
+      padding-top: 64px;
+      padding-bottom: 64px;
+    }
   }
 
   table {
@@ -63,6 +71,7 @@
     background-color: $gray-light;
     text-align: right;
     padding: 8px $gl-padding;
+    border-bottom: 1px solid $border-color;
 
     @media (max-width: $screen-xs-max) {
       text-align: left;
@@ -122,7 +131,7 @@
     }
 
     /**
-     *  Annotate file
+     *  Blame file
      */
     &.blame {
       table {
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index cfbaaaa04c74a0328650aa5e5f89d8c32bcc01bb..767cf5ffea58c2170809712589eb6b885f32dda0 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -152,7 +152,7 @@
   }
 
   .value-container {
-    background-color: $filter-value-selected-color;
+    box-shadow: inset 0 0 0 100px $filtered-search-term-shadow-color;
   }
 }
 
@@ -236,9 +236,6 @@
     width: 35px;
     background-color: $white-light;
     border: none;
-    position: static;
-    right: 0;
-    height: 100%;
     outline: none;
     z-index: 1;
 
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index a78179e727f2355bbc500ef1c6db8d5b1712d0db..61e3897f36966bd95668b55a708cf6895bd6437a 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -125,10 +125,11 @@ label {
 .select-wrapper {
   position: relative;
 
-  .fa-caret-down {
+  .fa-chevron-down {
     position: absolute;
+    font-size: 10px;
     right: 10px;
-    top: 10px;
+    top: 12px;
     color: $gray-darkest;
     pointer-events: none;
   }
@@ -138,6 +139,12 @@ label {
   padding-left: 10px;
   padding-right: 10px;
   -webkit-appearance: none;
+  -moz-appearance: none;
+  appearance: none;
+
+  &::-ms-expand {
+    display: none;
+  }
 }
 
 .form-control-inline {
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index d8645afb7da26b553a7ac63e644b2d67b84a404d..5bd6c095109809ec32efda215f4899051a4e4bf2 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -34,6 +34,8 @@ header {
     top: 0;
     left: 0;
     right: 0;
+    color: $gl-text-color-secondary;
+    border-radius: 0;
 
     @media (max-width: $screen-xs-min) {
       padding: 0 16px;
@@ -59,7 +61,7 @@ header {
       padding: 0;
 
       .nav > li > a {
-        color: $gl-text-color-secondary;
+        color: currentColor;
         font-size: 18px;
         padding: 0;
         margin: (($header-height - 28) / 2) 3px;
@@ -84,7 +86,7 @@ header {
         &:hover,
         &:focus,
         &:active {
-          background-color: $gray-light;
+          background-color: transparent;
           color: $gl-text-color;
 
           svg {
@@ -96,13 +98,19 @@ header {
           font-size: 14px;
         }
 
+        .fa-chevron-down {
+          position: relative;
+          top: -3px;
+          font-size: 10px;
+        }
+
         svg {
           position: relative;
           top: 2px;
           height: 17px;
           // hack to get SVG to line up with FA icons
           width: 23px;
-          fill: $gl-text-color-secondary;
+          fill: currentColor;
         }
       }
 
@@ -225,7 +233,7 @@ header {
       }
 
       a {
-        color: $gl-text-color;
+        color: currentColor;
 
         &:hover {
           text-decoration: underline;
@@ -346,6 +354,8 @@ header {
     width: auto;
     min-width: 140px;
     margin-top: -5px;
+    color: $gl-text-color;
+    left: auto;
 
     .current-user {
       padding: 5px 18px;
diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss
index 49bff23452df7a8da26195248a8c71db0f93c810..4a9d41b4fda1576fbb3ab1cdc073df2d548af97e 100644
--- a/app/assets/stylesheets/framework/layout.scss
+++ b/app/assets/stylesheets/framework/layout.scss
@@ -53,7 +53,7 @@ body {
   }
 
   &.limit-container-width-sm {
-    max-width: 790px;
+    max-width: $limited-layout-width-sm;
   }
 }
 
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index 80691a234f8cf1e10e5783eb94210ac4e39f846c..b21bcc22a8728e536b8188cf6f7a57de2d00e587 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -174,3 +174,14 @@
     white-space: nowrap;
   }
 }
+
+@media(max-width: $screen-xs-max) {
+  .atwho-view-ul {
+    width: 350px;
+  }
+
+  .atwho-view ul li {
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
+}
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index 3787ef370b2928820aaa005f981c3d7121270c32..28b2a7cfacd786b6c918e02ec308520ee3ff1620 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -45,8 +45,7 @@
   li {
     display: flex;
 
-    a,
-    .btn-link {
+    a {
       padding: $gl-btn-padding;
       padding-bottom: 11px;
       font-size: 14px;
@@ -68,29 +67,7 @@
       }
     }
 
-    .btn-link {
-      padding-top: 16px;
-      padding-left: 15px;
-      padding-right: 15px;
-      border-left: none;
-      border-right: none;
-      border-top: none;
-      border-radius: 0;
-
-      &:hover,
-      &:active,
-      &:focus {
-        background-color: transparent;
-      }
-
-      &:active {
-        outline: 0;
-        box-shadow: none;
-      }
-    }
-
-    &.active a,
-    &.active .btn-link {
+    &.active a {
       border-bottom: 2px solid $link-underline-blue;
       color: $black;
       font-weight: 600;
diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss
index fa364e68d22541620b70f3e819d38a245438a1e1..e8d69e621940dcf41da584a6f85392b77355533d 100644
--- a/app/assets/stylesheets/framework/panels.scss
+++ b/app/assets/stylesheets/framework/panels.scss
@@ -1,54 +1,60 @@
 .panel {
   margin-bottom: $gl-padding;
+}
+
+.panel-slim {
+  @extend .panel;
+  margin-bottom: $gl-vert-padding;
+}
+
+
+.panel-heading {
+  padding: $gl-vert-padding $gl-padding;
+  line-height: 36px;
+
+  .controls {
+    margin-top: -2px;
+    float: right;
+  }
+
+  .dropdown-menu-toggle {
+    line-height: 20px;
+  }
 
-  .panel-heading {
-    padding: $gl-vert-padding $gl-padding;
-    line-height: 36px;
-
-    .controls {
-      margin-top: -2px;
-      float: right;
-    }
-
-    .dropdown-menu-toggle {
-      line-height: 20px;
-    }
-
-    .badge {
-      margin-top: -2px;
-      margin-left: 5px;
-    }
-
-    &.split {
-      display: flex;
-      align-items: center;
-    }
-
-    .left {
-      flex: 1 1 auto;
-    }
-
-    .right {
-      flex: 0 0 auto;
-      text-align: right;
-    }
+  .badge {
+    margin-top: -2px;
+    margin-left: 5px;
   }
 
-  .panel-empty-heading {
-    border-bottom: 0;
+  &.split {
+    display: flex;
+    align-items: center;
   }
 
-  .panel-body {
-    padding: $gl-padding;
+  .left {
+    flex: 1 1 auto;
+  }
 
-    .form-actions {
-      margin: -$gl-padding;
-      margin-top: $gl-padding;
-    }
+  .right {
+    flex: 0 0 auto;
+    text-align: right;
   }
+}
+
+.panel-empty-heading {
+  border-bottom: 0;
+}
+
+.panel-body {
+  padding: $gl-padding;
 
-  .panel-title {
-    font-size: inherit;
-    line-height: inherit;
+  .form-actions {
+    margin: -$gl-padding;
+    margin-top: $gl-padding;
   }
 }
+
+.panel-title {
+  font-size: inherit;
+  line-height: inherit;
+}
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index 1b20c35ad98f97973eaad9dea2cad87c3bfd994a..40e654f4838e1635fb251c18c333a606ec34ecc6 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -18,19 +18,28 @@
       background-image: none;
       background-color: transparent;
       border: none;
-      padding-top: 6px;
-      padding-right: 10px;
+      padding-top: 12px;
+      padding-right: 20px;
+      font-size: 10px;
 
       b {
-        display: inline-block;
-        width: 0;
-        height: 0;
-        margin-left: 2px;
-        vertical-align: middle;
-        border-top: 5px dashed;
-        border-right: 5px solid transparent;
-        border-left: 5px solid transparent;
+        display: none;
+      }
+
+      &::after {
+        content: "\f078";
+        position: absolute;
+        z-index: 1;
+        text-align: center;
+        pointer-events: none;
+        box-sizing: border-box;
         color: $gray-darkest;
+        display: inline-block;
+        font: normal normal normal 14px/1 FontAwesome;
+        font-size: inherit;
+        text-rendering: auto;
+        -webkit-font-smoothing: antialiased;
+        -moz-osx-font-smoothing: grayscale;
       }
     }
 
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index d4421e3af7468851be63ef31a0601438a05858f1..542b641e3dd9de207eaf9f33fa351d53c3c26533 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -92,22 +92,24 @@
 @mixin maintain-sidebar-dimensions {
   display: block;
   width: $gutter-width;
-  padding: 10px 20px;
+  padding: 10px 0;
 }
 
 .issues-bulk-update.right-sidebar {
   @include maintain-sidebar-dimensions;
-  transition: right $sidebar-transition-duration;
-  right: -$gutter-width;
+  width: 0;
+  padding: 0;
+  transition: width $sidebar-transition-duration;
 
   &.right-sidebar-expanded {
     @include maintain-sidebar-dimensions;
-    right: 0;
+    width: $gutter-width;
   }
 
   &.right-sidebar-collapsed {
     @include maintain-sidebar-dimensions;
-    right: -$gutter-width;
+    width: 0;
+    padding: 0;
 
     .block {
       padding: 16px 0;
@@ -118,5 +120,6 @@
 
   .issuable-sidebar {
     padding: 0 3px;
+    width: calc(100% + 35px);
   }
 }
diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss
index 108819870382d40e491db8a8368c5832ffa2f553..3d68a50f91f73b9e567b8b94362ec4664485482b 100644
--- a/app/assets/stylesheets/framework/timeline.scss
+++ b/app/assets/stylesheets/framework/timeline.scss
@@ -44,6 +44,10 @@
   &:target,
   &.target {
     background: $line-target-blue;
+
+    &.system-note .note-body .note-text.system-note-commit-list::after {
+      background: linear-gradient(rgba($line-target-blue, 0.1) -100px, $line-target-blue 100%);
+    }
   }
 
   .avatar {
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 49ba0108228a343ba1c6b80988257851f234b6b7..da4d91511e0323c809857d2719fcc25827148ec9 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -74,6 +74,12 @@ $red-700: #a62d19;
 $red-800: #8b2615;
 $red-900: #711e11;
 
+$purple-600: #6e49cb;
+$purple-650: #5c35ae;
+$purple-700: #4a2192;
+$purple-800: #2c0a5c;
+$purple-900: #380d75;
+
 $black: #000;
 $black-transparent: rgba(0, 0, 0, 0.3);
 
@@ -99,6 +105,7 @@ $well-light-text-color: #5b6169;
  */
 $gl-font-size: 14px;
 $gl-text-color: rgba(0, 0, 0, .85);
+$gl-text-color-light: rgba(0, 0, 0, .7);
 $gl-text-color-secondary: rgba(0, 0, 0, .55);
 $gl-text-color-disabled: rgba(0, 0, 0, .35);
 $gl-text-color-inverted: rgba(255, 255, 255, 1.0);
@@ -161,6 +168,7 @@ $progress-color: #c0392b;
 $header-height: 50px;
 $fixed-layout-width: 1280px;
 $limited-layout-width: 990px;
+$limited-layout-width-sm: 790px;
 $gl-avatar-size: 40px;
 $error-exclamation-point: $red-500;
 $border-radius-default: 3px;
@@ -282,6 +290,7 @@ $dropdown-toggle-active-border-color: darken($border-color, 14%);
 /*
 * Filtered Search
 */
+$filtered-search-term-shadow-color: rgba(0, 0, 0, 0.09);
 $dropdown-hover-color: $blue-400;
 
 /*
@@ -322,6 +331,7 @@ $note-disabled-comment-color: #b2b2b2;
 $note-targe3-outside: #fffff0;
 $note-targe3-inside: #ffffd3;
 $note-line2-border: #ddd;
+$note-icon-gutter-width: 55px;
 
 
 /*
diff --git a/app/assets/stylesheets/framework/wells.scss b/app/assets/stylesheets/framework/wells.scss
index 1c1392f8f67c3094730f73483a76a05189dca0b3..b1ff2659131bc9e057082cf17afff148e3897e07 100644
--- a/app/assets/stylesheets/framework/wells.scss
+++ b/app/assets/stylesheets/framework/wells.scss
@@ -3,6 +3,7 @@
   color: $gl-text-color;
   border: 1px solid $border-color;
   border-radius: $border-radius-default;
+  margin-bottom: $gl-padding;
 
   .well-segment {
     padding: $gl-padding;
@@ -21,6 +22,11 @@
         font-size: 12px;
       }
     }
+
+    &.admin-well h4 {
+      border-bottom: 1px solid $border-color;
+      padding-bottom: 8px;
+    }
   }
 
   .icon-container {
@@ -53,6 +59,14 @@
   padding: 15px;
 }
 
+.dark-well {
+  background-color: $gray-normal;
+
+  .btn {
+    width: 100%;
+  }
+}
+
 .well-centered {
   h1 {
     font-weight: normal;
diff --git a/app/assets/stylesheets/new_nav.scss b/app/assets/stylesheets/new_nav.scss
new file mode 100644
index 0000000000000000000000000000000000000000..bfb7a0c7e25ebd9f3af38dd3d1358002d4719b19
--- /dev/null
+++ b/app/assets/stylesheets/new_nav.scss
@@ -0,0 +1,390 @@
+@import "framework/variables";
+@import 'framework/tw_bootstrap_variables';
+@import "bootstrap/variables";
+
+header.navbar-gitlab-new {
+  color: $white-light;
+  background-color: $purple-900;
+  border-bottom: 0;
+
+  .header-content {
+    padding-left: 0;
+
+    .title-container {
+      align-items: stretch;
+      padding-top: 0;
+      overflow: visible;
+    }
+
+    .title {
+      display: flex;
+      padding-right: 0;
+      color: currentColor;
+
+      > a {
+        display: flex;
+        align-items: center;
+        padding-top: 3px;
+        padding-right: $gl-padding;
+        padding-left: $gl-padding;
+        margin-left: -$gl-padding;
+        border-bottom: 3px solid transparent;
+
+        @media (min-width: $screen-sm-min) {
+          padding-right: $gl-padding;
+          padding-left: $gl-padding;
+        }
+
+        svg {
+          margin-top: -3px;
+
+          @media (min-width: $screen-sm-min) {
+            margin-right: 10px;
+          }
+        }
+
+        &:hover,
+        &:focus {
+          color: currentColor;
+          text-decoration: none;
+          border-bottom-color: $white-light;
+        }
+      }
+    }
+
+    .dropdown.open {
+      > a {
+        border-bottom-color: $white-light;
+      }
+    }
+
+    .dropdown-menu {
+      margin-top: 4px;
+      min-width: 130px;
+
+      @media (max-width: $screen-xs-max) {
+        left: auto;
+        right: 0;
+      }
+    }
+  }
+
+  .navbar-collapse {
+    padding-left: 0;
+    color: $white-light;
+    box-shadow: 0;
+
+    @media (max-width: $screen-xs-max) {
+      margin-left: -$gl-padding;
+      margin-right: -10px;
+    }
+
+    .dropdown-bold-header {
+      color: initial;
+    }
+
+    .nav {
+      > li:not(.hidden-xs) a {
+        @media (max-width: $screen-xs-max) {
+          margin-left: 0;
+          min-width: 100%;
+        }
+      }
+    }
+  }
+
+  .container-fluid {
+    .navbar-toggle {
+      min-width: 45px;
+      padding: 6px $gl-padding;
+      margin-right: -7px;
+      font-size: 14px;
+      text-align: center;
+      color: currentColor;
+      border-left: 1px solid lighten($purple-700, 10%);
+
+      &:hover,
+      &:focus,
+      &.active {
+        color: currentColor;
+        background-color: transparent;
+      }
+    }
+
+    .navbar-nav {
+      @media (max-width: $screen-xs-max) {
+        display: flex;
+        padding-right: 10px;
+      }
+
+      li {
+        .badge {
+          box-shadow: none;
+        }
+      }
+    }
+
+    .nav > li {
+      &.header-user {
+        @media (max-width: $screen-xs-max) {
+          padding-left: 10px;
+        }
+      }
+
+      > a {
+        background: none;
+        opacity: .9;
+        will-change: opacity;
+
+        &.header-user-dropdown-toggle {
+          .header-user-avatar {
+            border-color: $white-light;
+          }
+        }
+
+        &:hover,
+        &:focus {
+          color: $white-light;
+          opacity: 1;
+
+          > svg {
+            fill: $white-light;
+          }
+
+          &.header-user-dropdown-toggle {
+            .header-user-avatar {
+              border-color: $white-light;
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+.navbar-sub-nav {
+  display: flex;
+  margin-bottom: 0;
+  color: $white-light;
+
+  > li {
+    &.active > a,
+    a:hover,
+    a:focus {
+      border-bottom-color: $white-light;
+      text-decoration: none;
+      outline: 0;
+      opacity: 1;
+    }
+
+    > a {
+      display: block;
+      padding: 16px 10px 13px;
+      font-size: 13px;
+      color: currentColor;
+      border-bottom: 3px solid transparent;
+      opacity: .9;
+      will-change: opacity;
+
+      @media (min-width: $screen-sm-min) {
+        padding: 15px $gl-padding 12px;
+        font-size: 14px;
+      }
+    }
+  }
+
+  .dropdown-chevron {
+    position: relative;
+    top: -1px;
+    font-size: 10px;
+  }
+}
+
+.header-user .dropdown-menu-nav,
+.header-new .dropdown-menu-nav {
+  margin-top: 4px;
+}
+
+.search {
+  form {
+    border-color: $purple-800;
+
+    &:hover {
+      border-color: rgba($white-light, .6);
+      box-shadow: none;
+    }
+  }
+
+  &.search-active form {
+    border-color: $white-light;
+  }
+
+  form,
+  .search-input {
+    background-color: $purple-700;
+  }
+
+  .search-input {
+    color: $white-light;
+  }
+
+  .search-input::placeholder {
+    color: rgba($white-light, .6);
+  }
+
+  .location-badge {
+    font-size: 12px;
+    color: rgba($white-light, .6);
+    background-color: $purple-800;
+    transition: color 0.15s;
+    will-change: color;
+  }
+
+  .search-input-wrap {
+    .search-icon,
+    .clear-icon {
+      color: rgba($white-light, .6);
+    }
+  }
+
+  &.search-active {
+    .location-badge {
+      color: $white-light;
+      background-color: $purple-800;
+    }
+
+    .search-input-wrap {
+      .search-icon {
+        color: rgba($white-light, .6);
+      }
+
+      .clear-icon {
+        color: $white-light;
+      }
+    }
+  }
+}
+
+.breadcrumbs {
+  display: flex;
+  min-height: 60px;
+  padding-top: $gl-padding-top;
+  padding-bottom: $gl-padding-top;
+  color: $gl-text-color;
+  border-bottom: 1px solid $border-color;
+
+  .dropdown-toggle-caret {
+    position: relative;
+    top: -1px;
+    padding: 0 5px;
+    color: rgba($black, .65);
+    font-size: 10px;
+    line-height: 1;
+    background: none;
+    border: 0;
+
+    &:focus {
+      outline: 0;
+    }
+  }
+}
+
+.breadcrumbs-container {
+  display: flex;
+  width: 100%;
+  position: relative;
+
+  .dropdown-menu-projects {
+    margin-top: -$gl-padding;
+    margin-left: $gl-padding;
+  }
+}
+
+.breadcrumbs-links {
+  flex: 1;
+  align-self: center;
+  color: $black-transparent;
+
+  a {
+    color: rgba($black, .65);
+
+    &:not(:first-child),
+    &.group-path {
+      margin-left: 4px;
+    }
+
+    &:not(:last-of-type),
+    &.group-path {
+      margin-right: 3px;
+    }
+  }
+
+  .title {
+    white-space: nowrap;
+
+    > a {
+      &:last-of-type {
+        font-weight: 600;
+      }
+    }
+  }
+
+  .avatar-tile {
+    margin-right: 5px;
+    border: 1px solid $border-color;
+    border-radius: 50%;
+    vertical-align: sub;
+
+    &.identicon {
+      float: left;
+      width: 16px;
+      height: 16px;
+      margin-top: 2px;
+      font-size: 10px;
+    }
+  }
+
+  .text-expander {
+    margin-left: 4px;
+    margin-right: 4px;
+
+    > i {
+      position: relative;
+      top: 1px;
+    }
+  }
+}
+
+.breadcrumbs-extra {
+  flex: 0 0 auto;
+  margin-left: auto;
+}
+
+.breadcrumbs-sub-title {
+  margin: 2px 0 0;
+  font-size: 16px;
+  font-weight: normal;
+
+  ul {
+    margin: 0;
+  }
+
+  li {
+    display: inline-block;
+
+    &:not(:last-child) {
+      &::after {
+        content: "/";
+        margin: 0 2px 0 5px;
+      }
+    }
+
+    &:last-child a {
+      font-weight: 600;
+    }
+  }
+
+  a {
+    color: $gl-text-color;
+  }
+}
diff --git a/app/assets/stylesheets/new_sidebar.scss b/app/assets/stylesheets/new_sidebar.scss
new file mode 100644
index 0000000000000000000000000000000000000000..17f23f7fce366261ffeabf270e23b627d7cf7d3d
--- /dev/null
+++ b/app/assets/stylesheets/new_sidebar.scss
@@ -0,0 +1,157 @@
+@import "framework/variables";
+@import 'framework/tw_bootstrap_variables';
+@import "bootstrap/variables";
+
+$new-sidebar-width: 220px;
+
+.page-with-new-sidebar {
+  @media (min-width: $screen-sm-min) {
+    padding-left: $new-sidebar-width;
+  }
+
+  // Override position: absolute
+  .right-sidebar {
+    position: fixed;
+    height: 100%;
+  }
+}
+
+.context-header {
+  background-color: $gray-normal;
+  border-bottom: 1px solid $border-color;
+  font-weight: 600;
+  display: flex;
+  align-items: center;
+  padding: 10px 14px;
+
+  .avatar-container {
+    flex: 0 0 40px;
+  }
+
+  &:hover {
+    background-color: $border-color;
+  }
+}
+
+.settings-avatar {
+  background-color: $white-light;
+
+  i {
+    font-size: 20px;
+    width: 100%;
+    color: $gl-text-color-secondary;
+    text-align: center;
+    align-self: center;
+  }
+}
+
+.nav-sidebar {
+  position: fixed;
+  z-index: 400;
+  width: $new-sidebar-width;
+  transition: width $sidebar-transition-duration;
+  top: 50px;
+  bottom: 0;
+  left: 0;
+  overflow: auto;
+  background-color: $gray-light;
+  border-right: 1px solid $border-color;
+
+  ul {
+    padding: 0;
+    list-style: none;
+  }
+
+  li {
+    white-space: nowrap;
+
+    a {
+      display: block;
+      padding: 12px 14px;
+    }
+  }
+
+  a {
+    color: $gl-text-color;
+    text-decoration: none;
+  }
+
+  @media (max-width: $screen-xs-max) {
+    width: 0;
+  }
+}
+
+.sidebar-sub-level-items {
+  display: none;
+
+  > li {
+    a {
+      padding: 12px 24px;
+      color: $gl-text-color-light;
+
+      &:hover {
+        color: $gl-text-color;
+        background-color: $border-color;
+      }
+    }
+
+    &.active {
+      > a {
+        color: $purple-650;
+        font-weight: 600;
+      }
+    }
+  }
+}
+
+.sidebar-top-level-items {
+  > li {
+    .badge {
+      float: right;
+      background-color: $border-color;
+      color: $gl-text-color;
+    }
+
+    &.active {
+      > a {
+        background-color: $purple-600;
+        color: $white-light;
+        font-weight: 600;
+      }
+
+      .badge {
+        background-color: $purple-700;
+        color: $white-light;
+      }
+
+      .sidebar-sub-level-items {
+        background-color: $gray-normal;
+        border-left: 6px solid $purple-600;
+        display: block;
+      }
+    }
+
+    &:not(.active) > a:hover {
+      background-color: $border-color;
+
+      .badge {
+        transition: background-color 100ms linear;
+        background-color: $gray-normal;
+      }
+    }
+  }
+}
+
+
+// Make issue boards full-height now that sub-nav is gone
+
+.boards-list {
+  height: calc(100vh - 50px);
+
+  @media (min-width: $screen-sm-min) {
+    height: 475px; // Needed for PhantomJS
+    // scss-lint:disable DuplicateProperty
+    height: calc(100vh - 120px);
+    // scss-lint:enable DuplicateProperty
+  }
+}
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 7eee0a71c664c83b639355157983e26ac2df9bac..23c06eca3c3680ffc037a23e0dbca31d2603e4a7 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -37,65 +37,77 @@
 }
 
 .build-page {
-  .sticky {
-    position: absolute;
-    left: 0;
-    right: 0;
+  .build-trace-container {
+    position: relative;
   }
 
-  .build-trace-container {
-    position: absolute;
-    top: 225px;
-    left: 15px;
-    bottom: 10px;
+  .build-trace {
     background: $black;
     color: $gray-darkest;
-    font-family: $monospace_font;
+    white-space: pre;
+    overflow-x: auto;
     font-size: 12px;
+    border-radius: 0;
+    border: none;
 
-    &.sidebar-expanded {
-      right: 305px;
+    .bash {
+      display: block;
     }
+  }
 
-    &.sidebar-collapsed {
-      right: 16px;
+  .top-bar {
+    height: 35px;
+    display: flex;
+    justify-content: flex-end;
+    background: $gray-light;
+    border: 1px solid $border-color;
+    color: $gl-text-color;
+    position: sticky;
+    position: -webkit-sticky;
+    top: 50px;
+
+    &.affix {
+      top: 50px;
     }
 
-    code {
-      background: $black;
-      color: $gray-darkest;
+    // with sidebar
+    &.affix.sidebar-expanded {
+      right: 306px;
+      left: 16px;
     }
 
-    .top-bar {
-      top: 0;
-      height: 35px;
-      display: flex;
-      justify-content: flex-end;
-      background: $gray-light;
-      border: 1px solid $border-color;
-      color: $gl-text-color;
+    // without sidebar
+    &.affix.sidebar-collapsed {
+      right: 16px;
+      left: 16px;
+    }
 
-      .truncated-info {
-        margin: 0 auto;
-        align-self: center;
+    &.affix-top {
+      position: absolute;
+      right: 0;
+      left: 0;
+    }
 
-        .truncated-info-size {
-          margin: 0 5px;
-        }
+    .truncated-info {
+      margin: 0 auto;
+      align-self: center;
 
-        .raw-link {
-          color: $gl-text-color;
-          margin-left: 5px;
-          text-decoration: underline;
-        }
+      .truncated-info-size {
+        margin: 0 5px;
+      }
+
+      .raw-link {
+        color: $gl-text-color;
+        margin-left: 5px;
+        text-decoration: underline;
       }
     }
 
     .controllers {
       display: flex;
-      align-self: center;
       font-size: 15px;
-      margin-bottom: 4px;
+      justify-content: center;
+      align-items: center;
 
       svg {
         height: 15px;
@@ -103,17 +115,9 @@
         fill: $gl-text-color;
       }
 
-      .controllers-buttons,
-      .btn-scroll {
-        color: $gl-text-color;
-        height: 15px;
-        vertical-align: middle;
-        padding: 0;
-        width: 12px;
-      }
-
       .controllers-buttons {
-        margin: 1px 10px;
+        color: $gl-text-color;
+        margin: 0 10px;
       }
 
       .btn-scroll.animate {
@@ -143,16 +147,6 @@
     }
   }
 
-  .bash {
-    top: 35px;
-    left: 10px;
-    bottom: 0;
-    overflow-y: scroll;
-    overflow-x: hidden;
-    padding: 10px 20px 20px 5px;
-    white-space: pre;
-  }
-
   .environment-information {
     border: 1px solid $border-color;
     padding: 8px $gl-padding 12px;
@@ -399,6 +393,7 @@
 
 .build-light-text {
   color: $gl-text-color-secondary;
+  word-wrap: break-word;
 }
 
 .build-gutter-toggle {
diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss
index 7bec4bd5f566e9f00c26c847603fe4d044e6dfe5..3039732ca5bdde7cd9c044d6e1ed11641d804087 100644
--- a/app/assets/stylesheets/pages/cycle_analytics.scss
+++ b/app/assets/stylesheets/pages/cycle_analytics.scss
@@ -4,7 +4,7 @@
   position: relative;
 
   .landing {
-    margin-top: 10px;
+    margin-top: 0;
 
     .inner-content {
       white-space: normal;
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index b58922626fa72fc90dd2a88f3507aa2679609e5a..55011e8a21b7f385504cb9a8147714fc4953c969 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -20,8 +20,6 @@
   }
 
   .diff-content {
-    overflow: auto;
-    overflow-y: hidden;
     background: $white-light;
     color: $gl-text-color;
     border-radius: 0 0 3px 3px;
@@ -476,6 +474,7 @@
   height: 19px;
   width: 19px;
   margin-left: -15px;
+  z-index: 100;
 
   &:hover {
     .diff-comment-avatar,
@@ -491,7 +490,7 @@
           transform: translateX((($i * $x-pos) - $x-pos));
 
           &:hover {
-            transform: translateX((($i * $x-pos) - $x-pos)) scale(1.2);
+            transform: translateX((($i * $x-pos) - $x-pos));
           }
         }
       }
@@ -542,6 +541,7 @@
   height: 19px;
   padding: 0;
   transition: transform .1s ease-out;
+  z-index: 100;
 
   svg {
     position: absolute;
@@ -555,10 +555,6 @@
     fill: $white-light;
   }
 
-  &:hover {
-    transform: scale(1.2);
-  }
-
   &:focus {
     outline: 0;
   }
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index 89bd437b36221eb4cbd7022dc199e632859f8c61..e9a679b20c28938444b642315a183b8af7b2a474 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -83,6 +83,7 @@
 
       .avatar {
         float: none;
+        margin-right: 0;
       }
     }
 
@@ -139,23 +140,6 @@
   }
 }
 
-.prometheus-graph {
-  text {
-    fill: $gl-text-color;
-    stroke-width: 0;
-  }
-
-  .label-axis-text,
-  .text-metric-usage {
-    fill: $black;
-    font-weight: 500;
-  }
-
-  .legend-axis-text {
-    fill: $black;
-  }
-}
-
 .x-axis path,
 .y-axis path,
 .label-x-axis-line,
@@ -204,6 +188,7 @@
 
 .text-metric {
   font-weight: 600;
+  font-size: 14px;
 }
 
 .selected-metric-line {
@@ -213,20 +198,15 @@
 
 .deployment-line {
   stroke: $black;
-  stroke-width: 2;
+  stroke-width: 1;
 }
 
 .deploy-info-text {
   dominant-baseline: text-before-edge;
 }
 
-.text-metric-bold {
-  font-weight: 600;
-}
-
 .prometheus-state {
   margin-top: 10px;
-  display: none;
 
   .state-button-section {
     margin-top: 10px;
@@ -241,3 +221,69 @@
     width: 38px;
   }
 }
+
+.prometheus-panel {
+  margin-top: 20px;
+}
+
+.prometheus-svg-container {
+  position: relative;
+  height: 0;
+  width: 100%;
+  padding: 0;
+  padding-bottom: 100%;
+
+  .text-metric-bold {
+    font-weight: 600;
+  }
+}
+
+.prometheus-svg-container > svg {
+  position: absolute;
+  height: 100%;
+  width: 100%;
+  left: 0;
+  top: 0;
+
+  text {
+    fill: $gl-text-color;
+    stroke-width: 0;
+  }
+
+  .label-axis-text,
+  .text-metric-usage {
+    fill: $black;
+    font-weight: 500;
+    font-size: 12px;
+  }
+
+  .legend-axis-text {
+    fill: $black;
+  }
+
+  .tick > text {
+    font-size: 12px;
+  }
+
+  .text-metric-title {
+    font-size: 12px;
+  }
+
+  @media (max-width: $screen-sm-max) {
+    .label-axis-text,
+    .text-metric-usage,
+    .legend-axis-text {
+      font-size: 8px;
+    }
+
+    .tick > text {
+      font-size: 8px;
+    }
+  }
+}
+
+.prometheus-row {
+  h5 {
+    font-size: 16px;
+  }
+}
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index 72d73b89a2aae0152a16d137a0cbd5193e3aba9f..6f6c683997581c32c568743d28ea6995f066eee8 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -90,8 +90,6 @@
 }
 
 .explore-groups.landing {
-  margin-top: 10px;
-
   .inner-content {
     padding: 0;
 
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index b3f310ff67d964b91f62985507711bad1ea8b6b4..47f500837265b0f9e50592a9fb6f6383ff7c70e0 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -11,7 +11,9 @@
   .commit-box,
   .info-well,
   .commit-ci-menu,
-  .files-changed {
+  .files-changed,
+  .limited-header-width,
+  .limited-width-notes {
     @extend .fixed-width-container;
   }
 
@@ -198,13 +200,12 @@
   right: 0;
   transition: width .3s;
   background: $gray-light;
-  padding: 0 20px;
   z-index: 200;
   overflow: hidden;
 
   .issuable-sidebar {
     width: calc(100% + 100px);
-    height: 100%;
+    height: calc(100% - #{$header-height});
     overflow-y: scroll;
     overflow-x: hidden;
     -webkit-overflow-scrolling: touch;
@@ -222,10 +223,20 @@
       }
     }
 
+    .issuable-sidebar {
+      padding: 0 20px;
+    }
+
     .issuable-sidebar-header {
       padding-top: 10px;
     }
 
+    &:not(.issue-boards-sidebar):not([data-signed-in]) {
+      .issuable-sidebar-header {
+        display: none;
+      }
+    }
+
     .assign-yourself .btn-link {
       padding-left: 0;
     }
@@ -247,6 +258,10 @@
       border-left: 1px solid $border-gray-normal;
     }
 
+    .title .gutter-toggle {
+      margin-top: 0;
+    }
+
     .assignee .avatar {
       float: left;
       margin-right: 10px;
@@ -585,7 +600,38 @@
     .issue-info-container {
       -webkit-flex: 1;
       flex: 1;
+      display: flex;
       padding-right: $gl-padding;
+
+      .issue-main-info {
+        flex: 1 auto;
+        margin-right: 10px;
+      }
+
+      .issuable-meta {
+        display: flex;
+        flex-direction: column;
+        align-items: flex-end;
+        flex: 1 0 auto;
+
+        .controls {
+          margin-bottom: 2px;
+          line-height: 20px;
+          padding: 0;
+        }
+
+        .issue-updated-at {
+          line-height: 20px;
+        }
+      }
+
+      @media(max-width: $screen-xs-max) {
+        .issuable-meta {
+          .controls li {
+            margin-right: 0;
+          }
+        }
+      }
     }
 
     .issue-check {
@@ -597,6 +643,30 @@
         vertical-align: text-top;
       }
     }
+
+    .issuable-milestone,
+    .issuable-info,
+    .task-status,
+    .issuable-updated-at {
+      font-weight: normal;
+      color: $gl-text-color-secondary;
+
+      a {
+        color: $gl-text-color;
+
+        .fa {
+          color: $gl-text-color-secondary;
+        }
+      }
+    }
+
+    @media(max-width: $screen-md-max) {
+      .task-status,
+      .issuable-due-date,
+      .project-ref-path {
+        display: none;
+      }
+    }
   }
 }
 
@@ -729,33 +799,3 @@
     }
   }
 }
-
-.confidential-issue-warning {
-  background-color: $gl-gray;
-  border-radius: 3px;
-  padding: $gl-btn-padding $gl-padding;
-  margin-top: $gl-padding-top;
-  font-size: 14px;
-  color: $white-light;
-
-  .fa {
-    margin-right: 8px;
-  }
-
-  a {
-    color: $white-light;
-    text-decoration: underline;
-  }
-
-  &.affix {
-    position: static;
-    width: initial;
-
-    @media (min-width: $screen-sm-min) {
-      position: sticky;
-      position: -webkit-sticky;
-      top: 60px;
-      z-index: 200;
-    }
-  }
-}
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index c10588ac58e6a0d791391b81c9cb5952c0dfba23..ee48f7a36262d5298b71a4e14584bdec3cb8fc18 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -138,6 +138,7 @@
     .fa {
       font-size: 18px;
       vertical-align: middle;
+      pointer-events: none;
     }
 
     &:hover {
@@ -278,5 +279,9 @@
 
 .label-link {
   display: inline-block;
-  vertical-align: text-top;
+  vertical-align: top;
+
+  .label {
+    vertical-align: inherit;
+  }
 }
diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss
index 4be0e133b69abab8e9bc6000fb1b35d946fbf94a..f21005895e4f259cfc0c2c315dd85faa4f92476a 100644
--- a/app/assets/stylesheets/pages/members.scss
+++ b/app/assets/stylesheets/pages/members.scss
@@ -136,10 +136,6 @@
       width: 250px;
     }
 
-    @media (min-width: $screen-md-min) {
-      width: 350px;
-    }
-
     &.input-short {
       @media (min-width: $screen-md-min) {
         width: 170px;
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 2dc7f73a295e79e31fa337e6c715e9f2b2c691e6..59e0624d94e3b0dc9143ad576f13fefda0ed41c2 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -419,7 +419,7 @@
 
   .commit {
     margin: 0;
-    padding: 10px 0;
+    padding: 10px;
     list-style: none;
 
     &:hover {
diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss
index 335e587b8f44b5a86d65a6a478e762bb621c1490..55e0ee1936eda792c3e26a0cdfccc576ff501730 100644
--- a/app/assets/stylesheets/pages/milestone.scss
+++ b/app/assets/stylesheets/pages/milestone.scss
@@ -111,8 +111,8 @@
   }
 }
 
-.issues-sortable-list,
-.merge_requests-sortable-list {
+.milestone-issues-list,
+.milestone-merge_requests-list {
   .issuable-detail {
     display: block;
     margin-top: 7px;
@@ -197,6 +197,4 @@
 
 .issuable-row {
   background-color: $white-light;
-  cursor: -webkit-grab;
-  cursor: grab;
 }
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index aa3074147379a1b7d2514c26ce5d2a70c4a03c44..9877ed2cfd69321cd89ae0b92a2e74648a1ac080 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -103,6 +103,42 @@
   }
 }
 
+.confidential-issue-warning {
+  background-color: $gray-normal;
+  border-radius: 3px;
+  padding: 3px 12px;
+  margin: auto;
+  margin-top: 0;
+  text-align: center;
+  font-size: 12px;
+  align-items: center;
+
+  @media (max-width: $screen-md-max) {
+    // On smaller devices the warning becomes the fourth item in the list,
+    // rather than centering, and grows to span the full width of the
+    // comment area.
+    order: 4;
+    margin: 6px auto;
+    width: 100%;
+  }
+
+  .fa {
+    margin-right: 8px;
+  }
+}
+
+.right-sidebar-expanded {
+  .confidential-issue-warning {
+    // When the sidebar is open the warning becomes the fourth item in the list,
+    // rather than centering, and grows to span the full width of the
+    // comment area.
+    order: 4;
+    margin: 6px auto;
+    width: 100%;
+  }
+}
+
+
 .discussion-form {
   padding: $gl-padding-top $gl-padding $gl-padding;
   background-color: $white-light;
@@ -112,8 +148,20 @@
   padding: 6px 0;
 }
 
-.notes-form > li {
-  border: 0;
+.notes.notes-form > li.timeline-entry {
+  @include notes-media('max', $screen-sm-max) {
+    padding: 0;
+  }
+
+  .timeline-content {
+    @include notes-media('max', $screen-sm-max) {
+      margin: 0;
+    }
+  }
+
+  .timeline-entry-inner {
+    border: 0;
+  }
 }
 
 .note-edit-form {
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index a04424633901eaeacb50f9f6cef03d62466c566b..303425041dfc1f2885d4ad30d38386e500e41476 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -14,16 +14,6 @@ ul.notes {
   margin: 0;
   padding: 0;
 
-  .timeline-content {
-    margin-left: 55px;
-
-    &.timeline-content-form {
-      @include notes-media('max', $screen-sm-max) {
-        margin-left: 0;
-      }
-    }
-  }
-
   .note-created-ago,
   .note-updated-at {
     white-space: nowrap;
@@ -46,15 +36,47 @@ ul.notes {
     }
   }
 
-  > li {
-    padding: $gl-padding $gl-btn-padding;
+  > li { // .timeline-entry
+    padding: 0;
     display: block;
     position: relative;
-    border-bottom: 1px solid $white-normal;
+    border-bottom: 0;
+
+    @include notes-media('min', $screen-sm-min) {
+      padding-left: $note-icon-gutter-width;
+    }
+
+    .timeline-entry-inner {
+      padding: $gl-padding $gl-btn-padding;
+      border-bottom: 1px solid $white-normal;
+    }
 
-    &:last-child {
-      // Override `.timeline > li:last-child { border-bottom: none; }`
+    &:target,
+    &.target {
       border-bottom: 1px solid $white-normal;
+
+      &:not(:first-child) {
+        border-top: 1px solid $white-normal;
+        margin-top: -1px;
+      }
+
+      .timeline-entry-inner {
+        border-bottom: 0;
+      }
+    }
+
+    .timeline-icon {
+      @include notes-media('min', $screen-sm-min) {
+        margin-left: -$note-icon-gutter-width;
+      }
+    }
+
+    .timeline-content {
+      margin-left: $note-icon-gutter-width;
+
+      @include notes-media('min', $screen-sm-min) {
+        margin-left: 0;
+      }
     }
 
     &.being-posted {
@@ -73,7 +95,7 @@ ul.notes {
     }
 
     &.note-discussion {
-      &.timeline-entry {
+      .timeline-entry-inner {
         padding: $gl-padding 10px;
       }
     }
@@ -152,13 +174,8 @@ ul.notes {
 
   .system-note {
     font-size: 14px;
-    padding-left: 0;
     clear: both;
 
-    @include notes-media('min', $screen-sm-min) {
-      margin-left: 65px;
-    }
-
     .note-header-info {
       padding-bottom: 0;
     }
@@ -192,13 +209,16 @@ ul.notes {
     .timeline-icon {
       float: left;
 
+      @include notes-media('min', $screen-sm-min) {
+        margin-left: 0;
+        width: auto;
+      }
+
       svg {
         width: 16px;
         height: 16px;
         fill: $gray-darkest;
-        position: absolute;
-        left: 0;
-        top: 2px;
+        margin-top: 2px;
       }
     }
 
@@ -250,7 +270,7 @@ ul.notes {
           &::after {
             content: '';
             width: 100%;
-            height: 67px;
+            height: 70px;
             position: absolute;
             left: 0;
             bottom: 0;
@@ -453,7 +473,7 @@ ul.notes {
 }
 
 .more-actions {
-  display: inline;
+  display: inline-block;
 
   .tooltip {
     white-space: nowrap;
@@ -509,11 +529,6 @@ ul.notes {
   display: inline;
   line-height: 20px;
 
-  @include notes-media('min', $screen-sm-min) {
-    margin-left: 10px;
-    line-height: 24px;
-  }
-
   .fa {
     color: $gray-darkest;
     position: relative;
@@ -613,8 +628,14 @@ ul.notes {
  * Line note button on the side of diffs
  */
 
+.line_holder .is-over:not(.no-comment-btn) {
+  .add-diff-note {
+    opacity: 1;
+  }
+}
+
 .add-diff-note {
-  display: none;
+  opacity: 0;
   margin-top: -2px;
   border-radius: 50%;
   background: $white-light;
@@ -627,13 +648,11 @@ ul.notes {
   width: 23px;
   height: 23px;
   border: 1px solid $blue-500;
-  transition: transform .1s ease-in-out;
 
   &:hover {
     background: $blue-500;
     border-color: $blue-600;
     color: $white-light;
-    transform: scale(1.15);
   }
 
   &:active {
@@ -644,15 +663,12 @@ ul.notes {
 .discussion-body,
 .diff-file {
   .notes .note {
-    padding-left: $gl-padding;
-    padding-right: $gl-padding;
-
-    &.system-note {
-      padding-left: 0;
+    border-bottom: 1px solid $white-normal;
 
-      @media (min-width: $screen-sm-min) {
-        margin-left: 70px;
-      }
+    .timeline-entry-inner {
+      padding-left: $gl-padding;
+      padding-right: $gl-padding;
+      border-bottom: none;
     }
   }
 }
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index a85ba3a59554ffbc34c163c483fbc540b24c94af..9637d26e56d58103164bb369cb479f6b2c978e97 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -133,7 +133,7 @@
       overflow: hidden;
       display: inline-block;
       white-space: nowrap;
-      vertical-align: top;
+      vertical-align: middle;
       text-overflow: ellipsis;
     }
 
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 062665bc6342f7810fa82cedd695862dbcda09b3..7d7c34115f9a77de5904c3e028717e8dcb0416a7 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -377,10 +377,11 @@ a.deploy-project-label {
 }
 
 .breadcrumb.repo-breadcrumb {
+  flex: 1;
   padding: 0;
   background: transparent;
   border: none;
-  line-height: 36px;
+  line-height: 34px;
   margin: 0;
 
   > li + li::before {
@@ -482,11 +483,12 @@ a.deploy-project-label {
 .project-stats {
   font-size: 0;
   text-align: center;
+  max-width: 100%;
+  border-bottom: 1px solid $border-color;
 
   .nav {
     padding-top: 12px;
     padding-bottom: 12px;
-    border-bottom: 1px solid $border-color;
   }
 
   .nav > li {
diff --git a/app/assets/stylesheets/pages/runners.scss b/app/assets/stylesheets/pages/runners.scss
index 9b6ff2375573291799cd12333c11490bb69762e6..57c73295d1e39a96b7e14ffe6fb44d8e42818868 100644
--- a/app/assets/stylesheets/pages/runners.scss
+++ b/app/assets/stylesheets/pages/runners.scss
@@ -33,3 +33,20 @@
     font-weight: normal;
   }
 }
+
+.admin-runner-btn-group-cell {
+  min-width: 150px;
+
+  .btn-sm {
+    padding: 4px 9px;
+  }
+
+  .btn-default {
+    color: $gl-text-color-secondary;
+  }
+
+  .fa-pause,
+  .fa-play {
+    font-size: 11px;
+  }
+}
diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss
index 33b3c083fd2d3685c573be8e46ae38381d43d30e..d69a8e0995c815b2fc9b8445bc0ad2764447efc7 100644
--- a/app/assets/stylesheets/pages/settings.scss
+++ b/app/assets/stylesheets/pages/settings.scss
@@ -29,6 +29,10 @@
   &:first-of-type {
     margin-top: 10px;
   }
+
+  &.expanded {
+    overflow: visible;
+  }
 }
 
 .settings-header {
@@ -122,3 +126,66 @@
     margin-left: 5px;
   }
 }
+
+.prometheus-metrics-monitoring {
+  .panel {
+    .panel-toggle {
+      width: 14px;
+    }
+
+    .badge {
+      font-size: inherit;
+    }
+
+    .panel-heading .badge-count {
+      color: $white-light;
+      background: $common-gray-dark;
+    }
+
+    .panel-body {
+      padding: 0;
+    }
+
+    .flash-container {
+      margin-bottom: 0;
+      cursor: default;
+
+      .flash-notice {
+        border-radius: 0;
+      }
+    }
+  }
+
+  .loading-metrics,
+  .empty-metrics {
+    padding: 30px 10px;
+
+    p,
+    .btn {
+      margin-top: 10px;
+      margin-bottom: 0;
+    }
+  }
+
+  .loading-metrics .metrics-load-spinner {
+    color: $loading-color;
+  }
+
+  .metrics-list {
+    margin-bottom: 0;
+
+    li {
+      padding: $gl-padding;
+
+      .badge {
+        margin-left: 5px;
+        background: $badge-bg;
+      }
+    }
+
+    /* Ensure we don't add border if there's only single li */
+    li + li {
+      border-top: 1px solid $border-color;
+    }
+  }
+}
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index ab63225147f2cb664e80a3af4c8a3703a2e1c9f3..dc88cf3e699f0137ed4742eabba410ff9777cd70 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -1,10 +1,83 @@
 .tree-holder {
-  > .nav-block {
-    margin: 11px 0;
+
+  .nav-block {
+    margin: 10px 0;
+
+    @media (min-width: $screen-sm-min) {
+      display: flex;
+
+      .tree-ref-container {
+        flex: 1;
+      }
+
+      .tree-controls {
+        text-align: right;
+
+        .btn-group {
+          margin-left: 10px;
+        }
+
+        .control {
+          float: left;
+          margin-left: 10px;
+        }
+      }
+
+      .tree-ref-holder {
+        float: left;
+        margin-right: 15px;
+      }
+
+      .repo-breadcrumb {
+        li:last-of-type {
+          position: relative;
+        }
+      }
+
+      .add-to-tree-dropdown {
+        position: absolute;
+        left: 18px;
+      }
+    }
+  }
+
+  @media (max-width: $screen-xs-max) {
+    .repo-breadcrumb {
+      margin-top: 10px;
+      position: relative;
+
+      .dropdown-menu {
+        min-width: 100%;
+        width: 100%;
+        left: inherit;
+        right: 0;
+      }
+    }
+
+    .add-to-tree-dropdown {
+      position: absolute;
+      left: 0;
+      right: 0;
+    }
+
+    .tree-controls {
+      margin-bottom: 10px;
+
+      .btn,
+      .dropdown,
+      .btn-group {
+        width: 100%;
+      }
+
+      .btn {
+        margin: 10px 0 0;
+      }
+    }
   }
 
   .file-finder {
-    width: 50%;
+    max-width: 500px;
+    width: 100%;
 
     .file-finder-input {
       width: 95%;
@@ -131,11 +204,6 @@
   }
 }
 
-.tree-ref-holder {
-  float: left;
-  margin-right: 15px;
-}
-
 .blob-commit-info {
   list-style: none;
   margin: 0;
@@ -159,16 +227,6 @@
   color: $md-link-color;
 }
 
-.tree-controls {
-  float: right;
-  position: relative;
-  z-index: 2;
-
-  .project-action-button {
-    margin-left: $btn-side-margin;
-  }
-}
-
 .repo-charts {
   .sub-header {
     margin: 20px 0;
diff --git a/app/controllers/abuse_reports_controller.rb b/app/controllers/abuse_reports_controller.rb
index 2eac0cabf7a01a1defd5919ed78bd7fd6d7d1fa0..ed13ead63f9b9b2f2c3be7bc2d1ea32d0e9bde7e 100644
--- a/app/controllers/abuse_reports_controller.rb
+++ b/app/controllers/abuse_reports_controller.rb
@@ -1,7 +1,9 @@
 class AbuseReportsController < ApplicationController
+  before_action :set_user, only: [:new]
+
   def new
     @abuse_report = AbuseReport.new
-    @abuse_report.user_id = params[:user_id]
+    @abuse_report.user_id = @user.id
     @ref_url = params.fetch(:ref_url, '')
   end
 
@@ -27,4 +29,14 @@ class AbuseReportsController < ApplicationController
       user_id
     ))
   end
+
+  def set_user
+    @user = User.find_by(id: params[:user_id])
+
+    if @user.nil?
+      redirect_to root_path, alert: "Cannot create the abuse report. The user has been deleted."
+    elsif @user.blocked?
+      redirect_to @user, alert: "Cannot create the abuse report. This user has been blocked."
+    end
+  end
 end
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 4d4b8a8425feab789aa3ca7df653c4d26abada4c..f978ce478c7a5712a434a66cc5dfaa98c5876f96 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -71,6 +71,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
     params[:application_setting][:disabled_oauth_sign_in_sources] =
       AuthHelper.button_based_providers.map(&:to_s) -
       Array(enabled_oauth_sign_in_sources)
+
+    params[:application_setting][:restricted_visibility_levels]&.delete("")
     params.delete(:domain_blacklist_raw) if params[:domain_blacklist_file]
 
     params.require(:application_setting).permit(
diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb
index a1975c0e341cc04d64ddee8f75d264e61bfa17f7..984d5398708403ffe930f913c094f206bbbab23d 100644
--- a/app/controllers/admin/projects_controller.rb
+++ b/app/controllers/admin/projects_controller.rb
@@ -40,14 +40,14 @@ class Admin::ProjectsController < Admin::ApplicationController
     ::Projects::TransferService.new(@project, current_user, params.dup).execute(namespace)
 
     @project.reload
-    redirect_to admin_namespace_project_path(@project.namespace, @project)
+    redirect_to admin_project_path(@project)
   end
 
   def repository_check
     RepositoryCheck::SingleRepositoryWorker.perform_async(@project.id)
 
     redirect_to(
-      admin_namespace_project_path(@project.namespace, @project),
+      admin_project_path(@project),
       notice: 'Repository check was triggered.'
     )
   end
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index b09eef17c239a1fd765d62f643ecafa95ae6eb78..fa1bc72560eed3fc3b15671555f04469ab0e82a6 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -54,7 +54,7 @@ class Admin::UsersController < Admin::ApplicationController
   end
 
   def block
-    if user.block
+    if update_user { |user| user.block }
       redirect_back_or_admin_user(notice: "Successfully blocked")
     else
       redirect_back_or_admin_user(alert: "Error occurred. User was not blocked")
@@ -64,7 +64,7 @@ class Admin::UsersController < Admin::ApplicationController
   def unblock
     if user.ldap_blocked?
       redirect_back_or_admin_user(alert: "This user cannot be unlocked manually from GitLab")
-    elsif user.activate
+    elsif update_user { |user| user.activate }
       redirect_back_or_admin_user(notice: "Successfully unblocked")
     else
       redirect_back_or_admin_user(alert: "Error occurred. User was not unblocked")
@@ -72,7 +72,7 @@ class Admin::UsersController < Admin::ApplicationController
   end
 
   def unlock
-    if user.unlock_access!
+    if update_user { |user| user.unlock_access! }
       redirect_back_or_admin_user(alert: "Successfully unlocked")
     else
       redirect_back_or_admin_user(alert: "Error occurred. User was not unlocked")
@@ -80,7 +80,7 @@ class Admin::UsersController < Admin::ApplicationController
   end
 
   def confirm
-    if user.confirm
+    if update_user { |user| user.confirm }
       redirect_back_or_admin_user(notice: "Successfully confirmed")
     else
       redirect_back_or_admin_user(alert: "Error occurred. User was not confirmed")
@@ -88,7 +88,8 @@ class Admin::UsersController < Admin::ApplicationController
   end
 
   def disable_two_factor
-    user.disable_two_factor!
+    update_user { |user| user.disable_two_factor! }
+
     redirect_to admin_user_path(user),
       notice: 'Two-factor Authentication has been disabled for this user'
   end
@@ -124,15 +125,18 @@ class Admin::UsersController < Admin::ApplicationController
     end
 
     respond_to do |format|
-      user.skip_reconfirmation!
-      if user.update_attributes(user_params_with_pass)
+      result = Users::UpdateService.new(user, user_params_with_pass).execute do |user|
+        user.skip_reconfirmation!
+      end
+
+      if result[:status] == :success
         format.html { redirect_to [:admin, user], notice: 'User was successfully updated.' }
         format.json { head :ok }
       else
         # restore username to keep form action url.
         user.username = params[:id]
         format.html { render "edit" }
-        format.json { render json: user.errors, status: :unprocessable_entity }
+        format.json { render json: [result[:message]], status: result[:status] }
       end
     end
   end
@@ -148,13 +152,16 @@ class Admin::UsersController < Admin::ApplicationController
 
   def remove_email
     email = user.emails.find(params[:email_id])
-    email.destroy
-
-    user.update_secondary_emails!
+    success = Emails::DestroyService.new(user, email: email.email).execute
 
     respond_to do |format|
-      format.html { redirect_back_or_admin_user(notice: "Successfully removed email.") }
-      format.js { head :ok }
+      if success
+        format.html { redirect_back_or_admin_user(notice: 'Successfully removed email.') }
+        format.json { head :ok }
+      else
+        format.html { redirect_back_or_admin_user(alert: 'There was an error removing the e-mail.') }
+        format.json { render json: 'There was an error removing the e-mail.', status: 400 }
+      end
     end
   end
 
@@ -202,4 +209,10 @@ class Admin::UsersController < Admin::ApplicationController
       :website_url
     ]
   end
+
+  def update_user(&block)
+    result = Users::UpdateService.new(user).execute(&block)
+
+    result[:status] == :success
+  end
 end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 91694ebcd1d3f2baff9b2a79ffea1a876c0adf63..b4c0cd0487f83132adbf3db7ec78bf67c75167a7 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -40,6 +40,10 @@ class ApplicationController < ActionController::Base
     render_404
   end
 
+  rescue_from(ActionController::UnknownFormat) do
+    render_404
+  end
+
   rescue_from Gitlab::Access::AccessDeniedError do |exception|
     render_403
   end
@@ -106,6 +110,8 @@ class ApplicationController < ActionController::Base
   end
 
   def log_exception(exception)
+    Raven.capture_exception(exception) if sentry_enabled?
+
     application_trace = ActionDispatch::ExceptionWrapper.new(env, exception).application_trace
     application_trace.map!{ |t| "  #{t}\n" }
     logger.error "\n#{exception.class.name} (#{exception.message}):\n#{application_trace.join}"
diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb
index 36ad307a93b04e25a64e8d223f6eea158dbb1846..782f0be9c4afa380a7dd0b273b526d40b747907c 100644
--- a/app/controllers/concerns/creates_commit.rb
+++ b/app/controllers/concerns/creates_commit.rb
@@ -78,8 +78,7 @@ module CreatesCommit
   end
 
   def new_merge_request_path
-    new_namespace_project_merge_request_path(
-      @project_to_commit_into.namespace,
+    project_new_merge_request_path(
       @project_to_commit_into,
       merge_request: {
         source_project_id: @project_to_commit_into.id,
@@ -91,14 +90,14 @@ module CreatesCommit
   end
 
   def existing_merge_request_path
-    namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
+    project_merge_request_path(@project, @merge_request)
   end
 
   def merge_request_exists?
     return @merge_request if defined?(@merge_request)
 
-    @merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.
-      find_by(source_project_id: @project_to_commit_into, source_branch: @branch_name, target_branch: @start_branch)
+    @merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened
+      .find_by(source_project_id: @project_to_commit_into, source_branch: @branch_name, target_branch: @start_branch)
   end
 
   def different_project?
diff --git a/app/controllers/concerns/membership_actions.rb b/app/controllers/concerns/membership_actions.rb
index 8d07780f6c2f32e8565c3a92a0c87f3b6ed7f0f4..47d9ae350ae6fb17b774d9550efe951fa699e957 100644
--- a/app/controllers/concerns/membership_actions.rb
+++ b/app/controllers/concerns/membership_actions.rb
@@ -15,8 +15,8 @@ module MembershipActions
   end
 
   def destroy
-    Members::DestroyService.new(membershipable, current_user, params).
-      execute(:all)
+    Members::DestroyService.new(membershipable, current_user, params)
+      .execute(:all)
 
     respond_to do |format|
       format.html do
@@ -42,8 +42,8 @@ module MembershipActions
   end
 
   def leave
-    member = Members::DestroyService.new(membershipable, current_user, user_id: current_user.id).
-      execute(:all)
+    member = Members::DestroyService.new(membershipable, current_user, user_id: current_user.id)
+      .execute(:all)
 
     notice =
       if member.request?
diff --git a/app/controllers/concerns/milestone_actions.rb b/app/controllers/concerns/milestone_actions.rb
index b2536a1c9497033df0d4f6e9ffcf2ed0fb403a33..081f33367801e6e9f1df1d1043d83087f0814b75 100644
--- a/app/controllers/concerns/milestone_actions.rb
+++ b/app/controllers/concerns/milestone_actions.rb
@@ -6,7 +6,7 @@ module MilestoneActions
       format.html { redirect_to milestone_redirect_path }
       format.json do
         render json: tabs_json("shared/milestones/_merge_requests_tab", {
-          merge_requests: @milestone.merge_requests,
+          merge_requests: @milestone.sorted_merge_requests,
           show_project_name: true
         })
       end
@@ -45,7 +45,7 @@ module MilestoneActions
 
   def milestone_redirect_path
     if @project
-      namespace_project_milestone_path(@project.namespace, @project, @milestone)
+      project_milestone_path(@project, @milestone)
     elsif @group
       group_milestone_path(@group, @milestone.safe_title, title: @milestone.title)
     else
diff --git a/app/controllers/concerns/repository_settings_redirect.rb b/app/controllers/concerns/repository_settings_redirect.rb
index 0854c73a02fec2df6526d9c3ad09525c20ff8a90..0576f0e6e7043e2d5e5286b942e3fd884d88ad5d 100644
--- a/app/controllers/concerns/repository_settings_redirect.rb
+++ b/app/controllers/concerns/repository_settings_redirect.rb
@@ -2,6 +2,6 @@ module RepositorySettingsRedirect
   extend ActiveSupport::Concern
 
   def redirect_to_repository_settings(project)
-    redirect_to namespace_project_settings_repository_path(project.namespace, project)
+    redirect_to project_settings_repository_path(project)
   end
 end
diff --git a/app/controllers/concerns/spammable_actions.rb b/app/controllers/concerns/spammable_actions.rb
index b68d76aeff0591eba73d9833f6f68789770bf38b..ada0dde87fb6350306d1d4d6dc0be814270592b8 100644
--- a/app/controllers/concerns/spammable_actions.rb
+++ b/app/controllers/concerns/spammable_actions.rb
@@ -9,9 +9,9 @@ module SpammableActions
 
   def mark_as_spam
     if SpamService.new(spammable).mark_as_spam!
-      redirect_to spammable, notice: "#{spammable.spammable_entity_type.titlecase} was submitted to Akismet successfully."
+      redirect_to spammable_path, 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.'
+      redirect_to spammable_path, alert: 'Error with Akismet. Please check the logs for more info.'
     end
   end
 
@@ -25,7 +25,7 @@ module SpammableActions
 
   def recaptcha_check_with_fallback(&fallback)
     if spammable.valid?
-      redirect_to spammable
+      redirect_to spammable_path
     elsif render_recaptcha?
       ensure_spam_config_loaded!
 
@@ -56,6 +56,10 @@ module SpammableActions
     raise NotImplementedError, "#{self.class} does not implement #{__method__}"
   end
 
+  def spammable_path
+    raise NotImplementedError, "#{self.class} does not implement #{__method__}"
+  end
+
   def authorize_submit_spammable!
     access_denied! unless current_user.admin?
   end
diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
index 641c502dbe4dcdd9e9025404da186fff46a1b83b..91c1e4dff79eafa96574c6712ea52ad0f75586c2 100644
--- a/app/controllers/dashboard/projects_controller.rb
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -22,8 +22,8 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
   end
 
   def starred
-    @projects = load_projects(params.merge(starred: true)).
-      includes(:forked_from_project, :tags).page(params[:page])
+    @projects = load_projects(params.merge(starred: true))
+      .includes(:forked_from_project, :tags).page(params[:page])
 
     @groups = []
 
@@ -45,8 +45,8 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
   end
 
   def load_projects(finder_params)
-    ProjectsFinder.new(params: finder_params, current_user: current_user).
-      execute.includes(:route, namespace: :route)
+    ProjectsFinder.new(params: finder_params, current_user: current_user)
+      .execute.includes(:route, namespace: :route)
   end
 
   def load_events
diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb
index 8f1870759e4a05e749a9466c159b26a7a08f6b2c..741879dee35b679beb46daed69ec71c60939e914 100644
--- a/app/controllers/explore/projects_controller.rb
+++ b/app/controllers/explore/projects_controller.rb
@@ -49,7 +49,7 @@ class Explore::ProjectsController < Explore::ApplicationController
   private
 
   def load_projects
-    ProjectsFinder.new(current_user: current_user, params: params).
-      execute.includes(:route, namespace: :route)
+    ProjectsFinder.new(current_user: current_user, params: params)
+      .execute.includes(:route, namespace: :route)
   end
 end
diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb
index e52fa7660447321b35156941802c5720d288519e..6b1d418fc9a07f11e55088923883786cfe584b03 100644
--- a/app/controllers/groups/milestones_controller.rb
+++ b/app/controllers/groups/milestones_controller.rb
@@ -11,6 +11,9 @@ class Groups::MilestonesController < Groups::ApplicationController
         @milestone_states = GlobalMilestone.states_count(@projects)
         @milestones = Kaminari.paginate_array(milestones).page(params[:page])
       end
+      format.json do
+        render json: milestones.map { |m| m.for_display.slice(:title, :name) }
+      end
     end
   end
 
diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb
index 7625187c7beccb7efc5e25f6029d26859b4e22bd..0982a61902bbff98667a8cb2d7bc4c1cc31e7379 100644
--- a/app/controllers/invites_controller.rb
+++ b/app/controllers/invites_controller.rb
@@ -63,7 +63,7 @@ class InvitesController < ApplicationController
     when Project
       project = member.source
       label = "project #{project.name_with_namespace}"
-      path = namespace_project_path(project.namespace, project)
+      path = project_path(project)
     when Group
       group = member.source
       label = "group #{group.name}"
diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb
index 11db164b3fa301a7a5052dcc8e9622fd0f10804f..4bceb1d67a3a52ba242abd51ba1f678ba19f3974 100644
--- a/app/controllers/jwt_controller.rb
+++ b/app/controllers/jwt_controller.rb
@@ -11,8 +11,8 @@ class JwtController < ApplicationController
     service = SERVICES[params[:service]]
     return head :not_found unless service
 
-    result = service.new(@authentication_result.project, @authentication_result.actor, auth_params).
-      execute(authentication_abilities: @authentication_result.authentication_abilities)
+    result = service.new(@authentication_result.project, @authentication_result.actor, auth_params)
+      .execute(authentication_abilities: @authentication_result.authentication_abilities)
 
     render json: result, status: result[:http_status]
   end
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index 2a8c8ca4bad6a6b2b3da0ef3c25d5fbf75897f47..b82681b197ebddf033f4b31ae98d3e26c029b312 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -144,7 +144,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
   end
 
   def log_audit_event(user, options = {})
-    AuditEventService.new(user, user, options).
-      for_authentication.security_event
+    AuditEventService.new(user, user, options)
+      .for_authentication.security_event
   end
 end
diff --git a/app/controllers/profiles/avatars_controller.rb b/app/controllers/profiles/avatars_controller.rb
index 933e0f3bceb73c31b0194227d42a7acfd141fe42..408650aac548c0324dc9004f0edfd7abb0e49f36 100644
--- a/app/controllers/profiles/avatars_controller.rb
+++ b/app/controllers/profiles/avatars_controller.rb
@@ -1,9 +1,8 @@
 class Profiles::AvatarsController < Profiles::ApplicationController
   def destroy
     @user = current_user
-    @user.remove_avatar!
 
-    @user.save
+    Users::UpdateService.new(@user).execute { |user| user.remove_avatar! }
 
     redirect_to profile_path, status: 302
   end
diff --git a/app/controllers/profiles/emails_controller.rb b/app/controllers/profiles/emails_controller.rb
index 5655fb2ba0e7671b39c3ac8e13a1a6e1dcbadf87..17b66df43e792b9da46b39ba104abaae7873d706 100644
--- a/app/controllers/profiles/emails_controller.rb
+++ b/app/controllers/profiles/emails_controller.rb
@@ -5,9 +5,9 @@ class Profiles::EmailsController < Profiles::ApplicationController
   end
 
   def create
-    @email = current_user.emails.new(email_params)
+    @email = Emails::CreateService.new(current_user, email_params).execute
 
-    if @email.save
+    if @email.errors.blank?
       NotificationService.new.new_email(@email)
     else
       flash[:alert] = @email.errors.full_messages.first
@@ -18,9 +18,8 @@ class Profiles::EmailsController < Profiles::ApplicationController
 
   def destroy
     @email = current_user.emails.find(params[:id])
-    @email.destroy
 
-    current_user.update_secondary_emails!
+    Emails::DestroyService.new(current_user, email: @email.email).execute
 
     respond_to do |format|
       format.html { redirect_to profile_emails_url, status: 302 }
diff --git a/app/controllers/profiles/notifications_controller.rb b/app/controllers/profiles/notifications_controller.rb
index a271e2dfc4ba8a61161fe4cf65b4d079421251a4..960b75126023c4361ade50ac7853ae07dc427292 100644
--- a/app/controllers/profiles/notifications_controller.rb
+++ b/app/controllers/profiles/notifications_controller.rb
@@ -7,7 +7,9 @@ class Profiles::NotificationsController < Profiles::ApplicationController
   end
 
   def update
-    if current_user.update_attributes(user_params)
+    result = Users::UpdateService.new(current_user, user_params).execute
+
+    if result[:status] == :success
       flash[:notice] = "Notification settings saved"
     else
       flash[:alert] = "Failed to save new settings"
diff --git a/app/controllers/profiles/passwords_controller.rb b/app/controllers/profiles/passwords_controller.rb
index 6217ec5ecef196dcb4306250d6d9543663c38b74..10145bae0d352d7ac946e3bdf93d73ab815be553 100644
--- a/app/controllers/profiles/passwords_controller.rb
+++ b/app/controllers/profiles/passwords_controller.rb
@@ -15,17 +15,17 @@ class Profiles::PasswordsController < Profiles::ApplicationController
       return
     end
 
-    new_password = user_params[:password]
-    new_password_confirmation = user_params[:password_confirmation]
-
-    result = @user.update_attributes(
-      password: new_password,
-      password_confirmation: new_password_confirmation,
+    password_attributes = {
+      password: user_params[:password],
+      password_confirmation: user_params[:password_confirmation],
       password_automatically_set: false
-    )
+    }
+
+    result = Users::UpdateService.new(@user, password_attributes).execute
+
+    if result[:status] == :success
+      Users::UpdateService.new(@user, password_expires_at: nil).execute
 
-    if result
-      @user.update_attributes(password_expires_at: nil)
       redirect_to root_path, notice: 'Password successfully changed'
     else
       render :new
@@ -46,7 +46,9 @@ class Profiles::PasswordsController < Profiles::ApplicationController
       return
     end
 
-    if @user.update_attributes(password_attributes)
+    result = Users::UpdateService.new(@user, password_attributes).execute
+
+    if result[:status] == :success
       flash[:notice] = "Password was successfully updated. Please login with it"
       redirect_to new_user_session_path
     else
diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb
index 5414142e2df068bd0dbd1d1ffb9150f885813488..1e557c476384426d04cc35ef5f92b92bf50bb7f6 100644
--- a/app/controllers/profiles/preferences_controller.rb
+++ b/app/controllers/profiles/preferences_controller.rb
@@ -6,7 +6,9 @@ class Profiles::PreferencesController < Profiles::ApplicationController
 
   def update
     begin
-      if @user.update_attributes(preferences_params)
+      result = Users::UpdateService.new(user, preferences_params).execute
+
+      if result[:status] == :success
         flash[:notice] = 'Preferences saved.'
       else
         flash[:alert] = 'Failed to save preferences.'
diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb
index 313cdcd1c15c4aee0984dc5247f5e360496e2af5..1a4f77639e7293d89559d1bfc6ec4827a3051a3d 100644
--- a/app/controllers/profiles/two_factor_auths_controller.rb
+++ b/app/controllers/profiles/two_factor_auths_controller.rb
@@ -10,7 +10,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
       current_user.otp_grace_period_started_at = Time.current
     end
 
-    current_user.save! if current_user.changed?
+    Users::UpdateService.new(current_user).execute!
 
     if two_factor_authentication_required? && !current_user.two_factor_enabled?
       two_factor_authentication_reason(
@@ -41,9 +41,9 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
 
   def create
     if current_user.validate_and_consume_otp!(params[:pin_code])
-      current_user.otp_required_for_login = true
-      @codes = current_user.generate_otp_backup_codes!
-      current_user.save!
+      Users::UpdateService.new(current_user, otp_required_for_login: true).execute! do |user|
+        @codes = user.generate_otp_backup_codes!
+      end
 
       render 'create'
     else
@@ -70,8 +70,9 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
   end
 
   def codes
-    @codes = current_user.generate_otp_backup_codes!
-    current_user.save!
+    Users::UpdateService.new(current_user).execute! do |user|
+      @codes = user.generate_otp_backup_codes!
+    end
   end
 
   def destroy
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index 72f34930ca8d8d8ffebbfdba18254d9bdd2c7258..076076fd1b3610b882aa5a16dbc8a4441719f95d 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -12,55 +12,64 @@ class ProfilesController < Profiles::ApplicationController
     user_params.except!(:email) if @user.external_email?
 
     respond_to do |format|
-      if @user.update_attributes(user_params)
+      result = Users::UpdateService.new(@user, user_params).execute
+
+      if result[:status] == :success
         message = "Profile was successfully updated"
+
         format.html { redirect_back_or_default(default: { action: 'show' }, options: { notice: message }) }
         format.json { render json: { message: message } }
       else
-        message = @user.errors.full_messages.uniq.join('. ')
-        format.html { redirect_back_or_default(default: { action: 'show' }, options: { alert: "Failed to update profile. #{message}" }) }
-        format.json { render json: { message: message }, status: :unprocessable_entity }
+        format.html { redirect_back_or_default(default: { action: 'show' }, options: { alert: result[:message] }) }
+        format.json { render json: result }
       end
     end
   end
 
   def reset_private_token
-    if current_user.reset_authentication_token!
-      flash[:notice] = "Private token was successfully reset"
+    Users::UpdateService.new(@user).execute! do |user|
+      user.reset_authentication_token!
     end
 
+    flash[:notice] = "Private token was successfully reset"
+
     redirect_to profile_account_path
   end
 
   def reset_incoming_email_token
-    if current_user.reset_incoming_email_token!
-      flash[:notice] = "Incoming email token was successfully reset"
+    Users::UpdateService.new(@user).execute! do |user|
+      user.reset_incoming_email_token!
     end
 
+    flash[:notice] = "Incoming email token was successfully reset"
+
     redirect_to profile_account_path
   end
 
   def reset_rss_token
-    if current_user.reset_rss_token!
-      flash[:notice] = "RSS token was successfully reset"
+    Users::UpdateService.new(@user).execute! do |user|
+      user.reset_rss_token!
     end
 
+    flash[:notice] = "RSS token was successfully reset"
+
     redirect_to profile_account_path
   end
 
   def audit_log
-    @events = AuditEvent.where(entity_type: "User", entity_id: current_user.id).
-      order("created_at DESC").
-      page(params[:page])
+    @events = AuditEvent.where(entity_type: "User", entity_id: current_user.id)
+      .order("created_at DESC")
+      .page(params[:page])
   end
 
   def update_username
-    if @user.update_attributes(username: user_params[:username])
-      options = { notice: "Username successfully changed" }
-    else
-      message = @user.errors.full_messages.uniq.join('. ')
-      options = { alert: "Username change failed - #{message}" }
-    end
+    result = Users::UpdateService.new(@user, username: user_params[:username]).execute
+
+    options = if result[:status] == :success
+                { notice: "Username successfully changed" }
+              else
+                { alert: "Username change failed - #{result[:message]}" }
+              end
 
     redirect_back_or_default(default: { action: 'show' }, options: options)
   end
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index 603a51266da1c37d6e503267fbd6c745b25f79c3..95de3a446414f8d41cc9fe95d2288e51af35c674 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -53,9 +53,21 @@ class Projects::ApplicationController < ApplicationController
     end
   end
 
+  def check_project_feature_available!(feature)
+    render_404 unless project.feature_available?(feature, current_user)
+  end
+
+  def check_issuables_available!
+    render_404 unless project.feature_available?(:issues, current_user) ||
+        project.feature_available?(:merge_requests, current_user)
+  end
+
   def method_missing(method_sym, *arguments, &block)
-    if method_sym.to_s =~ /\Aauthorize_(.*)!\z/
+    case method_sym.to_s
+    when /\Aauthorize_(.*)!\z/
       authorize_action!($1.to_sym)
+    when /\Acheck_(.*)_available!\z/
+      check_project_feature_available!($1.to_sym)
     else
       super
     end
@@ -64,13 +76,13 @@ class Projects::ApplicationController < ApplicationController
   def require_non_empty_project
     # Be sure to return status code 303 to avoid a double DELETE:
     # http://api.rubyonrails.org/classes/ActionController/Redirecting.html
-    redirect_to namespace_project_path(@project.namespace, @project), status: 303 if @project.empty_repo?
+    redirect_to project_path(@project), status: 303 if @project.empty_repo?
   end
 
   def require_branch_head
     unless @repository.branch_exists?(@ref)
       redirect_to(
-        namespace_project_tree_path(@project.namespace, @project, @ref),
+        project_tree_path(@project, @ref),
         notice: "This action is not allowed unless you are on a branch"
       )
     end
diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb
index ea036b1f70541f2d9b61c49c87e5cc6f453aab51..f637a9a803bea85108c79b283151b6343c2f6f03 100644
--- a/app/controllers/projects/artifacts_controller.rb
+++ b/app/controllers/projects/artifacts_controller.rb
@@ -46,7 +46,7 @@ class Projects::ArtifactsController < Projects::ApplicationController
 
   def keep
     build.keep_artifacts!
-    redirect_to namespace_project_job_path(project.namespace, project, build)
+    redirect_to project_job_path(project, build)
   end
 
   def latest_succeeded
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 66e6a9a451cef3d7224b15f99752ae860a8348de..49ea2945675f105587453559faa00b30897d46d6 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -27,9 +27,9 @@ class Projects::BlobController < Projects::ApplicationController
 
   def create
     create_commit(Files::CreateService, success_notice: "The file has been successfully created.",
-                                        success_path: -> { namespace_project_blob_path(@project.namespace, @project, File.join(@branch_name, @file_path)) },
+                                        success_path: -> { project_blob_path(@project, File.join(@branch_name, @file_path)) },
                                         failure_view: :new,
-                                        failure_path: namespace_project_new_blob_path(@project.namespace, @project, @ref))
+                                        failure_path: project_new_blob_path(@project, @ref))
   end
 
   def show
@@ -63,7 +63,7 @@ class Projects::BlobController < Projects::ApplicationController
     @path = params[:file_path] if params[:file_path].present?
     create_commit(Files::UpdateService, success_path: -> { after_edit_path },
                                         failure_view: :edit,
-                                        failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
+                                        failure_path: project_blob_path(@project, @id))
 
   rescue Files::UpdateService::FileChangedError
     @conflict = true
@@ -83,9 +83,9 @@ class Projects::BlobController < Projects::ApplicationController
 
   def destroy
     create_commit(Files::DeleteService, success_notice: "The file has been successfully deleted.",
-                                        success_path: -> { namespace_project_tree_path(@project.namespace, @project, @branch_name) },
+                                        success_path: -> { project_tree_path(@project, @branch_name) },
                                         failure_view: :show,
-                                        failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
+                                        failure_path: project_blob_path(@project, @id))
   end
 
   def diff
@@ -118,7 +118,7 @@ class Projects::BlobController < Projects::ApplicationController
     else
       if tree = @repository.tree(@commit.id, @path)
         if tree.entries.any?
-          return redirect_to namespace_project_tree_path(@project.namespace, @project, File.join(@ref, @path))
+          return redirect_to project_tree_path(@project, File.join(@ref, @path))
         end
       end
 
@@ -143,10 +143,10 @@ class Projects::BlobController < Projects::ApplicationController
   def after_edit_path
     from_merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.find_by(iid: params[:from_merge_request_iid])
     if from_merge_request && @branch_name == @ref
-      diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) +
+      diffs_project_merge_request_path(from_merge_request.target_project, from_merge_request) +
         "##{hexdigest(@path)}"
     else
-      namespace_project_blob_path(@project.namespace, @project, File.join(@branch_name, @path))
+      project_blob_path(@project, File.join(@branch_name, @path))
     end
   end
 
@@ -187,7 +187,7 @@ class Projects::BlobController < Projects::ApplicationController
   end
 
   def set_last_commit_sha
-    @last_commit_sha = Gitlab::Git::Commit.
-      last_for_path(@repository, @ref, @path).sha
+    @last_commit_sha = Gitlab::Git::Commit
+      .last_for_path(@repository, @ref, @path).sha
   end
 end
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index 70b06cfd9b4c5b3547b394b83fe9cacc7d20a2db..8605853117905ee7155c7c8bd7d4c825fa149fca 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -37,8 +37,8 @@ class Projects::BranchesController < Projects::ApplicationController
 
     redirect_to_autodeploy = project.empty_repo? && project.deployment_services.present?
 
-    result = CreateBranchService.new(project, current_user).
-        execute(branch_name, ref)
+    result = CreateBranchService.new(project, current_user)
+        .execute(branch_name, ref)
 
     if params[:issue_iid]
       issue = IssuesFinder.new(current_user, project_id: @project.id).find_by(iid: params[:issue_iid])
@@ -52,7 +52,7 @@ class Projects::BranchesController < Projects::ApplicationController
             redirect_to url_to_autodeploy_setup(project, branch_name),
               notice: view_context.autodeploy_flash_notice(branch_name)
           else
-            redirect_to namespace_project_tree_path(@project.namespace, @project, branch_name)
+            redirect_to project_tree_path(@project, branch_name)
           end
         else
           @error = result[:message]
@@ -62,7 +62,7 @@ class Projects::BranchesController < Projects::ApplicationController
 
       format.json do
         if result[:status] == :success
-          render json: { name: branch_name, url: namespace_project_tree_url(@project.namespace, @project, branch_name) }
+          render json: { name: branch_name, url: project_tree_url(@project, branch_name) }
         else
           render json: result[:messsage], status: :unprocessable_entity
         end
@@ -79,7 +79,7 @@ class Projects::BranchesController < Projects::ApplicationController
         flash_type = result[:status] == :error ? :alert : :notice
         flash[flash_type] = result[:message]
 
-        redirect_to namespace_project_branches_path(@project.namespace, @project), status: 303
+        redirect_to project_branches_path(@project), status: 303
       end
 
       format.js { render nothing: true, status: result[:return_code] }
@@ -90,7 +90,7 @@ class Projects::BranchesController < Projects::ApplicationController
   def destroy_all_merged
     DeleteMergedBranchesService.new(@project, current_user).async_execute
 
-    redirect_to namespace_project_branches_path(@project.namespace, @project),
+    redirect_to project_branches_path(@project),
       notice: 'Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes.'
   end
 
@@ -106,8 +106,7 @@ class Projects::BranchesController < Projects::ApplicationController
   end
 
   def url_to_autodeploy_setup(project, branch_name)
-    namespace_project_new_blob_path(
-      project.namespace,
+    project_new_blob_path(
       project,
       branch_name,
       file_name: '.gitlab-ci.yml',
diff --git a/app/controllers/projects/build_artifacts_controller.rb b/app/controllers/projects/build_artifacts_controller.rb
index f34a198634e7f30fb1a5be1894c25a569087475e..b45e5d7ff43a67c1c1f3bcbe7a4de5f0a3dc544e 100644
--- a/app/controllers/projects/build_artifacts_controller.rb
+++ b/app/controllers/projects/build_artifacts_controller.rb
@@ -7,23 +7,23 @@ class Projects::BuildArtifactsController < Projects::ApplicationController
   before_action :validate_artifacts!
 
   def download
-    redirect_to download_namespace_project_job_artifacts_path(project.namespace, project, job)
+    redirect_to download_project_job_artifacts_path(project, job)
   end
 
   def browse
-    redirect_to browse_namespace_project_job_artifacts_path(project.namespace, project, job, path: params[:path])
+    redirect_to browse_project_job_artifacts_path(project, job, path: params[:path])
   end
 
   def file
-    redirect_to file_namespace_project_job_artifacts_path(project.namespace, project, job, path: params[:path])
+    redirect_to file_project_job_artifacts_path(project, job, path: params[:path])
   end
 
   def raw
-    redirect_to raw_namespace_project_job_artifacts_path(project.namespace, project, job, path: params[:path])
+    redirect_to raw_project_job_artifacts_path(project, job, path: params[:path])
   end
 
   def latest_succeeded
-    redirect_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, job, ref_name_and_path: params[:ref_name_and_path], job: params[:job])
+    redirect_to latest_succeeded_project_artifacts_path(project, job, ref_name_and_path: params[:ref_name_and_path], job: params[:job])
   end
 
   private
diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb
index 1334a231788bc74cc63e09d821c50403dd9b26ae..230b072dcea365092a0e5a68e5fbf34a4788ca9a 100644
--- a/app/controllers/projects/builds_controller.rb
+++ b/app/controllers/projects/builds_controller.rb
@@ -2,15 +2,15 @@ class Projects::BuildsController < Projects::ApplicationController
   before_action :authorize_read_build!
 
   def index
-    redirect_to namespace_project_jobs_path(project.namespace, project)
+    redirect_to project_jobs_path(project)
   end
 
   def show
-    redirect_to namespace_project_job_path(project.namespace, project, job)
+    redirect_to project_job_path(project, job)
   end
 
   def raw
-    redirect_to raw_namespace_project_job_path(project.namespace, project, job)
+    redirect_to raw_project_job_path(project, job)
   end
 
   private
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 7c3cce1c24119950815fdcac97112a2cd8ed0f00..14a1e11a6ea350955b280264e828f3f08a7512d1 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -80,16 +80,16 @@ class Projects::CommitController < Projects::ApplicationController
   end
 
   def successful_change_path
-    referenced_merge_request_url || namespace_project_commits_url(@project.namespace, @project, @branch_name)
+    referenced_merge_request_url || project_commits_url(@project, @branch_name)
   end
 
   def failed_change_path
-    referenced_merge_request_url || namespace_project_commit_url(@project.namespace, @project, params[:id])
+    referenced_merge_request_url || project_commit_url(@project, params[:id])
   end
 
   def referenced_merge_request_url
     if merge_request = @commit.merged_merge_request(current_user)
-      namespace_project_merge_request_url(merge_request.target_project.namespace, merge_request.target_project, merge_request)
+      project_merge_request_url(merge_request.target_project, merge_request)
     end
   end
 
diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb
index f33797ca310d2e81d936fea0585a3f2c55b61791..37b5a6e9d481b9735a7cf28e044cc1d868591e96 100644
--- a/app/controllers/projects/commits_controller.rb
+++ b/app/controllers/projects/commits_controller.rb
@@ -18,11 +18,11 @@ class Projects::CommitsController < Projects::ApplicationController
         @repository.commits(@ref, path: @path, limit: @limit, offset: @offset)
       end
 
-    @note_counts = project.notes.where(commit_id: @commits.map(&:id)).
-      group(:commit_id).count
+    @note_counts = project.notes.where(commit_id: @commits.map(&:id))
+      .group(:commit_id).count
 
-    @merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.
-      find_by(source_project: @project, source_branch: @ref, target_branch: @repository.root_ref)
+    @merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened
+      .find_by(source_project: @project, source_branch: @ref, target_branch: @repository.root_ref)
 
     respond_to do |format|
       format.html
diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb
index 88dd600e5fef9ca1758b2c5a4c03e5f2f79e8dc2..c8613c0d63476d210562c2bfd6c569e8448da92a 100644
--- a/app/controllers/projects/compare_controller.rb
+++ b/app/controllers/projects/compare_controller.rb
@@ -31,9 +31,9 @@ class Projects::CompareController < Projects::ApplicationController
         from: params[:from].presence,
         to: params[:to].presence
       }
-      redirect_to namespace_project_compare_index_path(@project.namespace, @project, from_to_vars)
+      redirect_to project_compare_index_path(@project, from_to_vars)
     else
-      redirect_to namespace_project_compare_path(@project.namespace, @project,
+      redirect_to project_compare_path(@project,
                                                params[:from], params[:to])
     end
   end
@@ -61,7 +61,7 @@ class Projects::CompareController < Projects::ApplicationController
   end
 
   def merge_request
-    @merge_request ||= MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.
-      find_by(source_project: @project, source_branch: @head_ref, target_branch: @start_ref)
+    @merge_request ||= MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened
+      .find_by(source_project: @project, source_branch: @head_ref, target_branch: @start_ref)
   end
 end
diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb
index 7f1469e107da38843b4a37755b09047c80ceb3d1..c2e621fa190bec3b4a4f37476751472ce9a7adcb 100644
--- a/app/controllers/projects/deploy_keys_controller.rb
+++ b/app/controllers/projects/deploy_keys_controller.rb
@@ -6,7 +6,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
   before_action :authorize_admin_project!
   before_action :authorize_update_deploy_key!, only: [:edit, :update]
 
-  layout "project_settings"
+  layout 'project_settings'
 
   def index
     respond_to do |format|
@@ -66,7 +66,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
   protected
 
   def deploy_key
-    @deploy_key ||= @project.deploy_keys.find(params[:id])
+    @deploy_key ||= DeployKey.find(params[:id])
   end
 
   def create_params
diff --git a/app/controllers/projects/deployments_controller.rb b/app/controllers/projects/deployments_controller.rb
index 6644deb49c9e430471ac00754fe6d3165d7fbe55..47c312ffddf9a930bc0bdc0531ad7939242ba8a4 100644
--- a/app/controllers/projects/deployments_controller.rb
+++ b/app/controllers/projects/deployments_controller.rb
@@ -22,6 +22,22 @@ class Projects::DeploymentsController < Projects::ApplicationController
     render_404
   end
 
+  def additional_metrics
+    return render_404 unless deployment.has_additional_metrics?
+
+    respond_to do |format|
+      format.json do
+        metrics = deployment.additional_metrics
+
+        if metrics.any?
+          render json: metrics
+        else
+          head :no_content
+        end
+      end
+    end
+  end
+
   private
 
   def deployment
diff --git a/app/controllers/projects/discussions_controller.rb b/app/controllers/projects/discussions_controller.rb
index f4a18a5e8f7a7b95f95563cb5d0b8d8ee0211e05..2e6ab7903b8c29fe1041c1e80d1c46aac9f40a81 100644
--- a/app/controllers/projects/discussions_controller.rb
+++ b/app/controllers/projects/discussions_controller.rb
@@ -1,5 +1,5 @@
 class Projects::DiscussionsController < Projects::ApplicationController
-  before_action :module_enabled
+  before_action :check_merge_requests_available!
   before_action :merge_request
   before_action :discussion
   before_action :authorize_resolve_discussion!
@@ -34,8 +34,4 @@ class Projects::DiscussionsController < Projects::ApplicationController
   def authorize_resolve_discussion!
     access_denied! unless discussion.can_resolve?(current_user)
   end
-
-  def module_enabled
-    render_404 unless @project.feature_available?(:merge_requests, current_user)
-  end
 end
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index 4630f451445632b31de0bfc92850156f5ca36a15..919d021b59c3d437a4eb3589434d93a66ac56863 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -15,8 +15,6 @@ class Projects::EnvironmentsController < Projects::ApplicationController
     respond_to do |format|
       format.html
       format.json do
-        Gitlab::PollingInterval.set_header(response, interval: 3_000)
-
         render json: {
           environments: EnvironmentSerializer
             .new(project: @project, current_user: @current_user)
@@ -65,7 +63,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
     @environment = project.environments.create(environment_params)
 
     if @environment.persisted?
-      redirect_to namespace_project_environment_path(project.namespace, project, @environment)
+      redirect_to project_environment_path(project, @environment)
     else
       render :new
     end
@@ -73,7 +71,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
 
   def update
     if @environment.update(environment_params)
-      redirect_to namespace_project_environment_path(project.namespace, project, @environment)
+      redirect_to project_environment_path(project, @environment)
     else
       render :edit
     end
@@ -88,7 +86,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
       if stop_action
         polymorphic_url([project.namespace.becomes(Namespace), project, stop_action])
       else
-        namespace_project_environment_url(project.namespace, project, @environment)
+        project_environment_url(project, @environment)
       end
 
     respond_to do |format|
@@ -131,6 +129,16 @@ class Projects::EnvironmentsController < Projects::ApplicationController
     end
   end
 
+  def additional_metrics
+    respond_to do |format|
+      format.json do
+        additional_metrics = environment.additional_metrics || {}
+
+        render json: additional_metrics, status: additional_metrics.any? ? :ok : :no_content
+      end
+    end
+  end
+
   private
 
   def verify_api_request!
diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb
index 1eb3800e49da42364169888e4e00f270ea5c1b08..3f83bef2c799c98a7de3f7a6b8605006e98d66fb 100644
--- a/app/controllers/projects/forks_controller.rb
+++ b/app/controllers/projects/forks_controller.rb
@@ -44,12 +44,12 @@ class Projects::ForksController < Projects::ApplicationController
 
     if @forked_project.saved? && @forked_project.forked?
       if @forked_project.import_in_progress?
-        redirect_to namespace_project_import_path(@forked_project.namespace, @forked_project, continue: continue_params)
+        redirect_to project_import_path(@forked_project, continue: continue_params)
       else
         if continue_params
           redirect_to continue_params[:to], notice: continue_params[:notice]
         else
-          redirect_to namespace_project_path(@forked_project.namespace, @forked_project), notice: "The project '#{@forked_project.name}' was successfully forked."
+          redirect_to project_path(@forked_project), notice: "The project '#{@forked_project.name}' was successfully forked."
         end
       end
     else
diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb
index 928f17e6a8e6e040e09c8bc6b67e40e9d60236e5..7d0e2b3e2efadfc5e236221abd0ff7eb622e1ade 100644
--- a/app/controllers/projects/git_http_client_controller.rb
+++ b/app/controllers/projects/git_http_client_controller.rb
@@ -4,7 +4,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController
   include ActionController::HttpAuthentication::Basic
   include KerberosSpnegoHelper
 
-  attr_reader :authentication_result
+  attr_reader :authentication_result, :redirected_path
 
   delegate :actor, :authentication_abilities, to: :authentication_result, allow_nil: true
 
@@ -14,7 +14,6 @@ class Projects::GitHttpClientController < Projects::ApplicationController
   skip_before_action :verify_authenticity_token
   skip_before_action :repository
   before_action :authenticate_user
-  before_action :ensure_project_found!
 
   private
 
@@ -68,38 +67,14 @@ class Projects::GitHttpClientController < Projects::ApplicationController
     headers['Www-Authenticate'] = challenges.join("\n") if challenges.any?
   end
 
-  def ensure_project_found!
-    render_not_found if project.blank?
-  end
-
   def project
-    return @project if defined?(@project)
-
-    project_id, _ = project_id_with_suffix
-    @project =
-      if project_id.blank?
-        nil
-      else
-        Project.find_by_full_path("#{params[:namespace_id]}/#{project_id}")
-      end
-  end
+    parse_repo_path unless defined?(@project)
 
-  # This method returns two values so that we can parse
-  # params[:project_id] (untrusted input!) in exactly one place.
-  def project_id_with_suffix
-    id = params[:project_id] || ''
-
-    %w[.wiki.git .git].each do |suffix|
-      if id.end_with?(suffix)
-        # Be careful to only remove the suffix from the end of 'id'.
-        # Accidentally removing it from the middle is how security
-        # vulnerabilities happen!
-        return [id.slice(0, id.length - suffix.length), suffix]
-      end
-    end
+    @project
+  end
 
-    # Something is wrong with params[:project_id]; do not pass it on.
-    [nil, nil]
+  def parse_repo_path
+    @project, @wiki, @redirected_path = Gitlab::RepoPath.parse("#{params[:namespace_id]}/#{params[:project_id]}")
   end
 
   def render_missing_personal_token
@@ -114,14 +89,9 @@ class Projects::GitHttpClientController < Projects::ApplicationController
   end
 
   def wiki?
-    return @wiki if defined?(@wiki)
-
-    _, suffix = project_id_with_suffix
-    @wiki = suffix == '.wiki.git'
-  end
+    parse_repo_path unless defined?(@wiki)
 
-  def render_not_found
-    render plain: 'Not Found', status: :not_found
+    @wiki
   end
 
   def handle_basic_authentication(login, password)
diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb
index b6b62da7b60ab20321050e2482cbf15955a10ca8..71ae60cb8cdacbb7578560a09124f2e62997da73 100644
--- a/app/controllers/projects/git_http_controller.rb
+++ b/app/controllers/projects/git_http_controller.rb
@@ -56,7 +56,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController
   end
 
   def access
-    @access ||= access_klass.new(access_actor, project, 'http', authentication_abilities: authentication_abilities)
+    @access ||= access_klass.new(access_actor, project, 'http', authentication_abilities: authentication_abilities, redirected_path: redirected_path)
   end
 
   def access_actor
diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb
index df5221fe95f2762550444b440ba38a7ea123745b..57372f9e79d1f4fee8157f79909bb77925350af6 100644
--- a/app/controllers/projects/graphs_controller.rb
+++ b/app/controllers/projects/graphs_controller.rb
@@ -29,7 +29,7 @@ class Projects::GraphsController < Projects::ApplicationController
   end
 
   def ci
-    redirect_to charts_namespace_project_pipelines_path(@project.namespace, @project)
+    redirect_to charts_project_pipelines_path(@project)
   end
 
   private
diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb
index deb33a2f0ff22b1dfac7ceee240fc2d033803a09..8fc614b414db7e1f2ea640edead107f148709dfd 100644
--- a/app/controllers/projects/group_links_controller.rb
+++ b/app/controllers/projects/group_links_controller.rb
@@ -22,7 +22,7 @@ class Projects::GroupLinksController < Projects::ApplicationController
       flash[:alert] = 'Please select a group.'
     end
 
-    redirect_to namespace_project_settings_members_path(project.namespace, project)
+    redirect_to project_settings_members_path(project)
   end
 
   def update
@@ -36,7 +36,7 @@ class Projects::GroupLinksController < Projects::ApplicationController
 
     respond_to do |format|
       format.html do
-        redirect_to namespace_project_settings_members_path(project.namespace, project), status: 302
+        redirect_to project_settings_members_path(project), status: 302
       end
       format.js { head :ok }
     end
diff --git a/app/controllers/projects/hook_logs_controller.rb b/app/controllers/projects/hook_logs_controller.rb
index 354f0d6db3a356ba7ebc9723a1d68d9ff1541769..b9c4b29580abae943ad7499d06425f86e778981a 100644
--- a/app/controllers/projects/hook_logs_controller.rb
+++ b/app/controllers/projects/hook_logs_controller.rb
@@ -18,7 +18,7 @@ class Projects::HookLogsController < Projects::ApplicationController
 
     set_hook_execution_notice(status, message)
 
-    redirect_to edit_namespace_project_hook_path(@project.namespace, @project, @hook)
+    redirect_to edit_project_hook_path(@project, @hook)
   end
 
   private
diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb
index f51432801545c225d6a75ce1a4850f492e94d13e..18895c3f0f3e32300b039a6775ae7603e2dce280 100644
--- a/app/controllers/projects/hooks_controller.rb
+++ b/app/controllers/projects/hooks_controller.rb
@@ -17,7 +17,7 @@ class Projects::HooksController < Projects::ApplicationController
       @hooks = @project.hooks.select(&:persisted?)
       flash[:alert] = @hook.errors.full_messages.join.html_safe
     end
-    redirect_to namespace_project_settings_integrations_path(@project.namespace, @project)
+    redirect_to project_settings_integrations_path(@project)
   end
 
   def edit
@@ -26,7 +26,7 @@ class Projects::HooksController < Projects::ApplicationController
   def update
     if hook.update_attributes(hook_params)
       flash[:notice] = 'Hook was successfully updated.'
-      redirect_to namespace_project_settings_integrations_path(@project.namespace, @project)
+      redirect_to project_settings_integrations_path(@project)
     else
       render 'edit'
     end
@@ -47,7 +47,7 @@ class Projects::HooksController < Projects::ApplicationController
   def destroy
     hook.destroy
 
-    redirect_to namespace_project_settings_integrations_path(@project.namespace, @project), status: 302
+    redirect_to project_settings_integrations_path(@project), status: 302
   end
 
   private
diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb
index 4b143434ea5ba700f152619fbcf1934c40439e24..49aa32119eff9ad76add9ad1fd80a0b2d547232e 100644
--- a/app/controllers/projects/imports_controller.rb
+++ b/app/controllers/projects/imports_controller.rb
@@ -17,7 +17,7 @@ class Projects::ImportsController < Projects::ApplicationController
       @project.reload.import_schedule
     end
 
-    redirect_to namespace_project_import_path(@project.namespace, @project)
+    redirect_to project_import_path(@project)
   end
 
   def show
@@ -25,10 +25,10 @@ class Projects::ImportsController < Projects::ApplicationController
       if continue_params
         redirect_to continue_params[:to], notice: continue_params[:notice]
       else
-        redirect_to namespace_project_path(@project.namespace, @project), notice: finished_notice
+        redirect_to project_path(@project), notice: finished_notice
       end
     elsif @project.import_failed?
-      redirect_to new_namespace_project_import_path(@project.namespace, @project)
+      redirect_to new_project_import_path(@project)
     else
       if continue_params && continue_params[:notice_now]
         flash.now[:notice] = continue_params[:notice_now]
@@ -50,19 +50,19 @@ class Projects::ImportsController < Projects::ApplicationController
 
   def require_no_repo
     if @project.repository_exists?
-      redirect_to namespace_project_path(@project.namespace, @project)
+      redirect_to project_path(@project)
     end
   end
 
   def redirect_if_progress
     if @project.import_in_progress?
-      redirect_to namespace_project_import_path(@project.namespace, @project)
+      redirect_to project_import_path(@project)
     end
   end
 
   def redirect_if_no_import
     if @project.repository_exists? && @project.no_import?
-      redirect_to namespace_project_path(@project.namespace, @project)
+      redirect_to project_path(@project)
     end
   end
 end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 56f76e752d0a3dd2f53e41617ccf42e6c510195b..c9e636fb65ed9ab51b755e12260fea7b8b243cf3 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -9,7 +9,7 @@ class Projects::IssuesController < Projects::ApplicationController
   prepend_before_action :authenticate_user!, only: [:new]
 
   before_action :redirect_to_external_issue_tracker, only: [:index, :new]
-  before_action :module_enabled
+  before_action :check_issues_available!
   before_action :issue, except: [:index, :new, :create, :bulk_update]
 
   # Allow write(create) issue
@@ -227,7 +227,7 @@ class Projects::IssuesController < Projects::ApplicationController
   def issue
     return @issue if defined?(@issue)
     # The Sortable default scope causes performance issues when used with find_by
-    @noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take!
+    @noteable = @issue ||= @project.issues.find_by!(iid: params[:id])
 
     return render_404 unless can?(current_user, :read_issue, @issue)
 
@@ -238,6 +238,10 @@ class Projects::IssuesController < Projects::ApplicationController
   alias_method :awardable, :issue
   alias_method :spammable, :issue
 
+  def spammable_path
+    project_issue_path(@project, @issue)
+  end
+
   def authorize_update_issue!
     return render_404 unless can?(current_user, :update_issue, @issue)
   end
@@ -250,7 +254,7 @@ class Projects::IssuesController < Projects::ApplicationController
     return render_404 unless can?(current_user, :push_code, @project) && @issue.can_be_worked_on?(current_user)
   end
 
-  def module_enabled
+  def check_issues_available!
     return render_404 unless @project.feature_available?(:issues, current_user) && @project.default_issues_tracker?
   end
 
@@ -267,10 +271,22 @@ class Projects::IssuesController < Projects::ApplicationController
   end
 
   def issue_params
-    params.require(:issue).permit(
-      :title, :assignee_id, :position, :description, :confidential,
-      :milestone_id, :due_date, :state_event, :task_num, :lock_version, label_ids: [], assignee_ids: []
-    )
+    params.require(:issue).permit(*issue_params_attributes)
+  end
+
+  def issue_params_attributes
+    %i[
+      title
+      assignee_id
+      position
+      description
+      confidential
+      milestone_id
+      due_date
+      state_event
+      task_num
+      lock_version
+    ] + [{ label_ids: [], assignee_ids: [] }]
   end
 
   def authenticate_user!
diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb
index cb4f46388fdc0f1c63f17811837fd5073623d64a..96abdac91b63187569f15ecc2d3b13ab09e42b81 100644
--- a/app/controllers/projects/jobs_controller.rb
+++ b/app/controllers/projects/jobs_controller.rb
@@ -38,7 +38,7 @@ class Projects::JobsController < Projects::ApplicationController
       build.cancel if can?(current_user, :update_build, build)
     end
 
-    redirect_to namespace_project_jobs_path(project.namespace, project)
+    redirect_to project_jobs_path(project)
   end
 
   def show
@@ -108,7 +108,7 @@ class Projects::JobsController < Projects::ApplicationController
 
   def erase
     if @build.erase(erased_by: current_user)
-      redirect_to namespace_project_job_path(project.namespace, project, @build),
+      redirect_to project_job_path(project, @build),
                 notice: "Build has been successfully erased!"
     else
       respond_422
@@ -137,6 +137,6 @@ class Projects::JobsController < Projects::ApplicationController
   end
 
   def build_path(build)
-    namespace_project_job_path(build.project.namespace, build.project, build)
+    project_job_path(build.project, build)
   end
 end
diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb
index 1beac202efe2f91d2552c678dce4948a3fe65b39..480a2dff262ff7c321d6039dd43ae038a4eadcf8 100644
--- a/app/controllers/projects/labels_controller.rb
+++ b/app/controllers/projects/labels_controller.rb
@@ -1,7 +1,7 @@
 class Projects::LabelsController < Projects::ApplicationController
   include ToggleSubscriptionAction
 
-  before_action :module_enabled
+  before_action :check_issuables_available!
   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!
@@ -33,7 +33,7 @@ class Projects::LabelsController < Projects::ApplicationController
 
     if @label.valid?
       respond_to do |format|
-        format.html { redirect_to namespace_project_labels_path(@project.namespace, @project) }
+        format.html { redirect_to project_labels_path(@project) }
         format.json { render json: @label }
       end
     else
@@ -51,7 +51,7 @@ class Projects::LabelsController < Projects::ApplicationController
     @label = Labels::UpdateService.new(label_params).execute(@label)
 
     if @label.valid?
-      redirect_to namespace_project_labels_path(@project.namespace, @project)
+      redirect_to project_labels_path(@project)
     else
       render :edit
     end
@@ -61,12 +61,11 @@ class Projects::LabelsController < Projects::ApplicationController
     Gitlab::IssuesLabels.generate(@project)
 
     if params[:redirect] == 'issues'
-      redirect_to namespace_project_issues_path(@project.namespace, @project)
+      redirect_to project_issues_path(@project)
     elsif params[:redirect] == 'merge_requests'
-      redirect_to namespace_project_merge_requests_path(@project.namespace,
-                                                        @project)
+      redirect_to project_merge_requests_path(@project)
     else
-      redirect_to namespace_project_labels_path(@project.namespace, @project)
+      redirect_to project_labels_path(@project)
     end
   end
 
@@ -74,7 +73,7 @@ class Projects::LabelsController < Projects::ApplicationController
     @label.destroy
     @labels = find_labels
 
-    redirect_to namespace_project_labels_path(@project.namespace, @project),
+    redirect_to project_labels_path(@project),
                 status: 302,
                 notice: 'Label was removed'
   end
@@ -114,7 +113,7 @@ class Projects::LabelsController < Projects::ApplicationController
       return render_404 unless promote_service.execute(@label)
       respond_to do |format|
         format.html do
-          redirect_to(namespace_project_labels_path(@project.namespace, @project),
+          redirect_to(project_labels_path(@project),
                       notice: 'Label was promoted to a Group Label')
         end
         format.js
@@ -125,7 +124,7 @@ class Projects::LabelsController < Projects::ApplicationController
 
       respond_to do |format|
         format.html do
-          redirect_to(namespace_project_labels_path(@project.namespace, @project),
+          redirect_to(project_labels_path(@project),
                       notice: 'Failed to promote label due to internal error. Please contact administrators.')
         end
         format.js
@@ -135,12 +134,6 @@ class Projects::LabelsController < Projects::ApplicationController
 
   protected
 
-  def module_enabled
-    unless @project.feature_available?(:issues, current_user) || @project.feature_available?(:merge_requests, current_user)
-      return render_404
-    end
-  end
-
   def label_params
     params.require(:label).permit(:title, :description, :color)
   end
diff --git a/app/controllers/projects/mattermosts_controller.rb b/app/controllers/projects/mattermosts_controller.rb
index 38f7e6eb5e9a4d64910524f9dfbd032009d75e0a..0f6add3e28790317bae06e408d726b8076eb04b8 100644
--- a/app/controllers/projects/mattermosts_controller.rb
+++ b/app/controllers/projects/mattermosts_controller.rb
@@ -16,12 +16,10 @@ class Projects::MattermostsController < Projects::ApplicationController
 
     if result
       flash[:notice] = 'This service is now configured'
-      redirect_to edit_namespace_project_service_path(
-        @project.namespace, @project, service)
+      redirect_to edit_project_service_path(@project, service)
     else
       flash[:alert] = message || 'Failed to configure service'
-      redirect_to new_namespace_project_mattermost_path(
-        @project.namespace, @project)
+      redirect_to new_project_mattermost_path(@project)
     end
   end
 
diff --git a/app/controllers/projects/merge_requests/application_controller.rb b/app/controllers/projects/merge_requests/application_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5de0f828010c2e3f4696a1ce5439a55d3f5ae33d
--- /dev/null
+++ b/app/controllers/projects/merge_requests/application_controller.rb
@@ -0,0 +1,48 @@
+class Projects::MergeRequests::ApplicationController < Projects::ApplicationController
+  before_action :check_merge_requests_available!
+  before_action :merge_request
+  before_action :authorize_read_merge_request!
+  before_action :ensure_ref_fetched
+
+  private
+
+  def merge_request
+    @issuable = @merge_request ||= @project.merge_requests.find_by!(iid: params[:id])
+  end
+
+  # Make sure merge requests created before 8.0
+  # have head file in refs/merge-requests/
+  def ensure_ref_fetched
+    @merge_request.ensure_ref_fetched
+  end
+
+  def merge_request_params
+    params.require(:merge_request)
+      .permit(merge_request_params_attributes)
+  end
+
+  def merge_request_params_attributes
+    [
+      :assignee_id,
+      :description,
+      :force_remove_source_branch,
+      :lock_version,
+      :milestone_id,
+      :source_branch,
+      :source_project_id,
+      :state_event,
+      :target_branch,
+      :target_project_id,
+      :task_num,
+      :title,
+
+      label_ids: []
+    ]
+  end
+
+  def set_pipeline_variables
+    @pipelines = @merge_request.all_pipelines
+    @pipeline = @merge_request.head_pipeline
+    @statuses_count = @pipeline.present? ? @pipeline.statuses.relevant.count : 0
+  end
+end
diff --git a/app/controllers/projects/merge_requests/conflicts_controller.rb b/app/controllers/projects/merge_requests/conflicts_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..28afef101a95ee6e0a13494f9e119844ae0cd59f
--- /dev/null
+++ b/app/controllers/projects/merge_requests/conflicts_controller.rb
@@ -0,0 +1,66 @@
+class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::ApplicationController
+  include IssuableActions
+
+  before_action :authorize_can_resolve_conflicts!
+
+  def show
+    respond_to do |format|
+      format.html do
+        labels
+      end
+
+      format.json do
+        if @conflicts_list.can_be_resolved_in_ui?
+          render json: @conflicts_list
+        elsif @merge_request.can_be_merged?
+          render json: {
+            message: 'The merge conflicts for this merge request have already been resolved. Please return to the merge request.',
+            type: 'error'
+          }
+        else
+          render json: {
+            message: 'The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally.',
+            type: 'error'
+          }
+        end
+      end
+    end
+  end
+
+  def conflict_for_path
+    return render_404 unless @conflicts_list.can_be_resolved_in_ui?
+
+    file = @conflicts_list.file_for_path(params[:old_path], params[:new_path])
+
+    return render_404 unless file
+
+    render json: file, full_content: true
+  end
+
+  def resolve_conflicts
+    return render_404 unless @conflicts_list.can_be_resolved_in_ui?
+
+    if @merge_request.can_be_merged?
+      render status: :bad_request, json: { message: 'The merge conflicts for this merge request have already been resolved.' }
+      return
+    end
+
+    begin
+      ::MergeRequests::Conflicts::ResolveService
+        .new(merge_request)
+        .execute(current_user, params)
+
+      flash[:notice] = 'All merge conflicts were resolved. The merge request can now be merged.'
+
+      render json: { redirect_to: project_merge_request_url(@project, @merge_request, resolved_conflicts: true) }
+    rescue Gitlab::Conflict::ResolutionError => e
+      render status: :bad_request, json: { message: e.message }
+    end
+  end
+
+  def authorize_can_resolve_conflicts!
+    @conflicts_list = ::MergeRequests::Conflicts::ListService.new(@merge_request)
+
+    return render_404 unless @conflicts_list.can_be_resolved_by?(current_user)
+  end
+end
diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..da058da795efef1f457c60e2f5fa0314b0ce2358
--- /dev/null
+++ b/app/controllers/projects/merge_requests/creations_controller.rb
@@ -0,0 +1,128 @@
+class Projects::MergeRequests::CreationsController < Projects::MergeRequests::ApplicationController
+  include DiffForPath
+  include DiffHelper
+
+  skip_before_action :merge_request
+  skip_before_action :ensure_ref_fetched
+  before_action :authorize_create_merge_request!
+  before_action :apply_diff_view_cookie!, only: [:diffs, :diff_for_path]
+  before_action :build_merge_request, except: [:create]
+
+  def new
+    define_new_vars
+  end
+
+  def create
+    @target_branches ||= []
+    @merge_request = ::MergeRequests::CreateService.new(project, current_user, merge_request_params).execute
+
+    if @merge_request.valid?
+      redirect_to(merge_request_path(@merge_request))
+    else
+      @source_project = @merge_request.source_project
+      @target_project = @merge_request.target_project
+
+      define_new_vars
+      render action: "new"
+    end
+  end
+
+  def pipelines
+    @pipelines = @merge_request.all_pipelines
+
+    Gitlab::PollingInterval.set_header(response, interval: 10_000)
+
+    render json: {
+      pipelines: PipelineSerializer
+      .new(project: @project, current_user: @current_user)
+      .represent(@pipelines)
+    }
+  end
+
+  def diffs
+    @diffs = if @merge_request.can_be_created
+               @merge_request.diffs(diff_options)
+             else
+               []
+             end
+    @diff_notes_disabled = true
+
+    @environment = @merge_request.environments_for(current_user).last
+
+    render json: { html: view_to_html_string('projects/merge_requests/creations/_diffs', diffs: @diffs, environment: @environment) }
+  end
+
+  def diff_for_path
+    @diffs = @merge_request.diffs(diff_options)
+    @diff_notes_disabled = true
+
+    render_diff_for_path(@diffs)
+  end
+
+  def branch_from
+    # This is always source
+    @source_project = @merge_request.nil? ? @project : @merge_request.source_project
+
+    if params[:ref].present?
+      @ref = params[:ref]
+      @commit = @repository.commit("refs/heads/#{@ref}")
+    end
+
+    render layout: false
+  end
+
+  def branch_to
+    @target_project = selected_target_project
+
+    if params[:ref].present?
+      @ref = params[:ref]
+      @commit = @target_project.commit("refs/heads/#{@ref}")
+    end
+
+    render layout: false
+  end
+
+  def update_branches
+    @target_project = selected_target_project
+    @target_branches = @target_project.repository.branch_names
+
+    render layout: false
+  end
+
+  private
+
+  def build_merge_request
+    params[:merge_request] ||= ActionController::Parameters.new(source_project: @project)
+    @merge_request = ::MergeRequests::BuildService.new(project, current_user, merge_request_params.merge(diff_options: diff_options)).execute
+  end
+
+  def define_new_vars
+    @noteable = @merge_request
+
+    @target_branches = if @merge_request.target_project
+                         @merge_request.target_project.repository.branch_names
+                       else
+                         []
+                       end
+
+    @target_project = @merge_request.target_project
+    @source_project = @merge_request.source_project
+    @commits = @merge_request.compare_commits.reverse
+    @commit = @merge_request.diff_head_commit
+
+    @note_counts = Note.where(commit_id: @commits.map(&:id))
+      .group(:commit_id).count
+
+    @labels = LabelsFinder.new(current_user, project_id: @project.id).execute
+
+    set_pipeline_variables
+  end
+
+  def selected_target_project
+    if @project.id.to_s == params[:target_project_id] || @project.forked_project_link.nil?
+      @project
+    else
+      @project.forked_project_link.forked_from_project
+    end
+  end
+end
diff --git a/app/controllers/projects/merge_requests/diffs_controller.rb b/app/controllers/projects/merge_requests/diffs_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..330b7df4541deffd803277188c4f7ee2327d5061
--- /dev/null
+++ b/app/controllers/projects/merge_requests/diffs_controller.rb
@@ -0,0 +1,66 @@
+class Projects::MergeRequests::DiffsController < Projects::MergeRequests::ApplicationController
+  include DiffForPath
+  include DiffHelper
+  include RendersNotes
+
+  before_action :apply_diff_view_cookie!
+  before_action :define_diff_vars
+  before_action :define_diff_comment_vars
+
+  def show
+    @environment = @merge_request.environments_for(current_user).last
+
+    render json: { html: view_to_html_string("projects/merge_requests/diffs/_diffs") }
+  end
+
+  def diff_for_path
+    render_diff_for_path(@diffs)
+  end
+
+  private
+
+  def define_diff_vars
+    @merge_request_diff =
+      if params[:diff_id]
+        @merge_request.merge_request_diffs.viewable.find(params[:diff_id])
+      else
+        @merge_request.merge_request_diff
+      end
+
+    @merge_request_diffs = @merge_request.merge_request_diffs.viewable.select_without_diff
+    @comparable_diffs = @merge_request_diffs.select { |diff| diff.id < @merge_request_diff.id }
+
+    if params[:start_sha].present?
+      @start_sha = params[:start_sha]
+      @start_version = @comparable_diffs.find { |diff| diff.head_commit_sha == @start_sha }
+
+      unless @start_version
+        @start_sha = @merge_request_diff.head_commit_sha
+        @start_version = @merge_request_diff
+      end
+    end
+
+    @compare =
+      if @start_sha
+        @merge_request_diff.compare_with(@start_sha)
+      else
+        @merge_request_diff
+      end
+
+    @diffs = @compare.diffs(diff_options)
+  end
+
+  def define_diff_comment_vars
+    @new_diff_note_attrs = {
+      noteable_type: 'MergeRequest',
+      noteable_id: @merge_request.id
+    }
+
+    @diff_notes_disabled = false
+
+    @use_legacy_diff_notes = !@merge_request.has_complete_diff_refs?
+
+    @grouped_diff_discussions = @merge_request.grouped_diff_discussions(@compare.diff_refs)
+    @notes = prepare_notes_for_rendering(@grouped_diff_discussions.values.flatten.flat_map(&:notes))
+  end
+end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 314906b5f09b54aa586819c85076817c80a4c192..a573b3925912290f34013d95d86e4105ad09887a 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -1,38 +1,17 @@
-class Projects::MergeRequestsController < Projects::ApplicationController
+class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationController
   include ToggleSubscriptionAction
-  include DiffForPath
-  include DiffHelper
   include IssuableActions
   include RendersNotes
   include ToggleAwardEmoji
   include IssuableCollections
 
-  before_action :module_enabled
-  before_action :merge_request, only: [
-    :edit, :update, :show, :diffs, :commits, :conflicts, :conflict_for_path, :pipelines, :merge,
-    :pipeline_status, :ci_environments_status, :toggle_subscription, :cancel_merge_when_pipeline_succeeds, :remove_wip, :resolve_conflicts, :assign_related_issues, :commit_change_content
-  ]
-  before_action :validates_merge_request, only: [:show, :diffs, :commits, :pipelines]
-  before_action :define_show_vars, only: [:diffs, :commits, :conflicts, :conflict_for_path, :builds, :pipelines]
-  before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds, :conflicts, :conflict_for_path, :pipelines]
-  before_action :close_merge_request_without_source_project, only: [:show, :diffs, :commits, :builds, :pipelines]
-  before_action :check_if_can_be_merged, only: :show
-  before_action :apply_diff_view_cookie!, only: [:new_diffs]
-  before_action :build_merge_request, only: [:new, :new_diffs]
-
-  # Allow read any merge_request
-  before_action :authorize_read_merge_request!
-
-  # Allow write(create) merge_request
-  before_action :authorize_create_merge_request!, only: [:new, :create]
-
-  # Allow modify merge_request
+  skip_before_action :merge_request, only: [:index, :bulk_update]
+  skip_before_action :ensure_ref_fetched, only: [:index, :bulk_update]
+
   before_action :authorize_update_merge_request!, only: [:close, :edit, :update, :remove_wip, :sort]
 
   before_action :authenticate_user!, only: [:assign_related_issues]
 
-  before_action :authorize_can_resolve_conflicts!, only: [:conflicts, :conflict_for_path, :resolve_conflicts]
-
   def index
     @collection_type    = "MergeRequest"
     @merge_requests     = merge_requests_collection
@@ -72,10 +51,30 @@ class Projects::MergeRequestsController < Projects::ApplicationController
   end
 
   def show
+    validates_merge_request
+    ensure_ref_fetched
+    close_merge_request_without_source_project
+    check_if_can_be_merged
+
     respond_to do |format|
       format.html do
-        define_discussion_vars
-        define_show_vars
+        # Build a note object for comment form
+        @note = @project.notes.new(noteable: @merge_request)
+
+        @discussions = @merge_request.discussions
+        @notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes))
+
+        @noteable = @merge_request
+        @commits_count = @merge_request.commits_count
+
+        if @merge_request.locked_long_ago?
+          @merge_request.unlock_mr
+          @merge_request.close
+        end
+
+        labels
+
+        set_pipeline_variables
       end
 
       format.json do
@@ -98,198 +97,40 @@ class Projects::MergeRequestsController < Projects::ApplicationController
     end
   end
 
-  def diffs
-    apply_diff_view_cookie!
-
-    respond_to do |format|
-      format.html { define_discussion_vars }
-      format.json do
-        define_diff_vars
-        define_diff_comment_vars
-
-        @environment = @merge_request.environments_for(current_user).last
-
-        render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") }
-      end
-    end
-  end
-
-  # With an ID param, loads the MR at that ID. Otherwise, accepts the same params as #new
-  # and uses that (unsaved) MR.
-  #
-  def diff_for_path
-    if params[:id]
-      merge_request
-      define_diff_vars
-      define_diff_comment_vars
-    else
-      build_merge_request
-      @compare = @merge_request
-      @diffs = @compare.diffs(diff_options)
-      @diff_notes_disabled = true
-    end
-
-    render_diff_for_path(@diffs)
-  end
-
   def commits
-    respond_to do |format|
-      format.html do
-        define_discussion_vars
-
-        render 'show'
-      end
-      format.json do
-        # Get commits from repository
-        # or from cache if already merged
-        @commits = @merge_request.commits
-        @note_counts = Note.where(commit_id: @commits.map(&:id)).
-          group(:commit_id).count
-
-        render json: { html: view_to_html_string('projects/merge_requests/show/_commits') }
-      end
-    end
-  end
-
-  def conflicts
-    respond_to do |format|
-      format.html { define_discussion_vars }
-
-      format.json do
-        if @conflicts_list.can_be_resolved_in_ui?
-          render json: @conflicts_list
-        elsif @merge_request.can_be_merged?
-          render json: {
-            message: 'The merge conflicts for this merge request have already been resolved. Please return to the merge request.',
-            type: 'error'
-          }
-        else
-          render json: {
-            message: 'The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally.',
-            type: 'error'
-          }
-        end
-      end
-    end
-  end
-
-  def conflict_for_path
-    return render_404 unless @conflicts_list.can_be_resolved_in_ui?
+    # Get commits from repository
+    # or from cache if already merged
+    @commits = @merge_request.commits
+    @note_counts = Note.where(commit_id: @commits.map(&:id))
+      .group(:commit_id).count
 
-    file = @conflicts_list.file_for_path(params[:old_path], params[:new_path])
-
-    return render_404 unless file
-
-    render json: file, full_content: true
-  end
-
-  def resolve_conflicts
-    return render_404 unless @conflicts_list.can_be_resolved_in_ui?
-
-    if @merge_request.can_be_merged?
-      render status: :bad_request, json: { message: 'The merge conflicts for this merge request have already been resolved.' }
-      return
-    end
-
-    begin
-      MergeRequests::Conflicts::ResolveService.
-        new(merge_request).
-        execute(current_user, params)
-
-      flash[:notice] = 'All merge conflicts were resolved. The merge request can now be merged.'
-
-      render json: { redirect_to: namespace_project_merge_request_url(@project.namespace, @project, @merge_request, resolved_conflicts: true) }
-    rescue Gitlab::Conflict::ResolutionError => e
-      render status: :bad_request, json: { message: e.message }
-    end
+    render json: { html: view_to_html_string('projects/merge_requests/_commits') }
   end
 
   def pipelines
     @pipelines = @merge_request.all_pipelines
 
-    respond_to do |format|
-      format.html do
-        define_discussion_vars
-
-        render 'show'
-      end
-
-      format.json do
-        Gitlab::PollingInterval.set_header(response, interval: 10_000)
-
-        render json: PipelineSerializer
-          .new(project: @project, current_user: @current_user)
-          .represent(@pipelines)
-      end
-    end
-  end
-
-  def new
-    respond_to do |format|
-      format.html { define_new_vars }
-      format.json do
-        define_pipelines_vars
-
-        Gitlab::PollingInterval.set_header(response, interval: 10_000)
-
-        render json: {
-          pipelines: PipelineSerializer
-          .new(project: @project, current_user: @current_user)
-          .represent(@pipelines)
-        }
-      end
-    end
-  end
-
-  def new_diffs
-    respond_to do |format|
-      format.html do
-        define_new_vars
-        @show_changes_tab = true
-        render "new"
-      end
-      format.json do
-        @diffs = if @merge_request.can_be_created
-                   @merge_request.diffs(diff_options)
-                 else
-                   []
-                 end
-        @diff_notes_disabled = true
-
-        @environment = @merge_request.environments_for(current_user).last
+    Gitlab::PollingInterval.set_header(response, interval: 10_000)
 
-        render json: { html: view_to_html_string('projects/merge_requests/_new_diffs', diffs: @diffs, environment: @environment) }
-      end
-    end
-  end
-
-  def create
-    @target_branches ||= []
-    @merge_request = MergeRequests::CreateService.new(project, current_user, merge_request_params).execute
-
-    if @merge_request.valid?
-      redirect_to(merge_request_path(@merge_request))
-    else
-      @source_project = @merge_request.source_project
-      @target_project = @merge_request.target_project
-      render action: "new"
-    end
+    render json: PipelineSerializer
+      .new(project: @project, current_user: @current_user)
+      .represent(@pipelines)
   end
 
   def edit
-    @source_project = @merge_request.source_project
-    @target_project = @merge_request.target_project
-    @target_branches = @merge_request.target_project.repository.branch_names
+    define_edit_vars
   end
 
   def update
-    @merge_request = MergeRequests::UpdateService.new(project, current_user, merge_request_params).execute(@merge_request)
+    @merge_request = ::MergeRequests::UpdateService.new(project, current_user, merge_request_params).execute(@merge_request)
 
     respond_to do |format|
       format.html do
         if @merge_request.valid?
           redirect_to([@merge_request.target_project.namespace.becomes(Namespace), @merge_request.target_project, @merge_request])
         else
+          define_edit_vars
+
           render :edit
         end
       end
@@ -299,11 +140,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController
       end
     end
   rescue ActiveRecord::StaleObjectError
+    define_edit_vars if request.format.html?
+
     render_conflict_response
   end
 
   def remove_wip
-    @merge_request = MergeRequests::UpdateService
+    @merge_request = ::MergeRequests::UpdateService
       .new(project, current_user, wip_event: 'unwip')
       .execute(@merge_request)
 
@@ -319,7 +162,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
       return access_denied!
     end
 
-    MergeRequests::MergeWhenPipelineSucceedsService
+    ::MergeRequests::MergeWhenPipelineSucceedsService
       .new(@project, current_user)
       .cancel(@merge_request)
 
@@ -338,53 +181,19 @@ class Projects::MergeRequestsController < Projects::ApplicationController
     end
   end
 
-  def branch_from
-    # This is always source
-    @source_project = @merge_request.nil? ? @project : @merge_request.source_project
-
-    if params[:ref].present?
-      @ref = params[:ref]
-      @commit = @repository.commit("refs/heads/#{@ref}")
-    end
-
-    render layout: false
-  end
-
-  def branch_to
-    @target_project = selected_target_project
-
-    if params[:ref].present?
-      @ref = params[:ref]
-      @commit = @target_project.commit("refs/heads/#{@ref}")
-    end
-
-    render layout: false
-  end
-
-  def update_branches
-    @target_project = selected_target_project
-    @target_branches = @target_project.repository.branch_names
-
-    render layout: false
-  end
-
   def assign_related_issues
-    result = MergeRequests::AssignIssuesService.new(project, current_user, merge_request: @merge_request).execute
-
-    respond_to do |format|
-      format.html do
-        case result[:count]
-        when 0
-          flash[:error] = "Failed to assign you issues related to the merge request"
-        when 1
-          flash[:notice] = "1 issue has been assigned to you"
-        else
-          flash[:notice] = "#{result[:count]} issues have been assigned to you"
-        end
+    result = ::MergeRequests::AssignIssuesService.new(project, current_user, merge_request: @merge_request).execute
 
-        redirect_to(merge_request_path(@merge_request))
-      end
+    case result[:count]
+    when 0
+      flash[:error] = "Failed to assign you issues related to the merge request"
+    when 1
+      flash[:notice] = "1 issue has been assigned to you"
+    else
+      flash[:notice] = "#{result[:count]} issues have been assigned to you"
     end
+
+    redirect_to(merge_request_path(@merge_request))
   end
 
   def pipeline_status
@@ -402,21 +211,18 @@ class Projects::MergeRequestsController < Projects::ApplicationController
 
           stop_url =
             if environment.stop_action? && can?(current_user, :create_deployment, environment)
-              stop_namespace_project_environment_path(project.namespace, project, environment)
+              stop_project_environment_path(project, environment)
             end
 
           metrics_url =
             if can?(current_user, :read_environment, environment) && environment.has_metrics?
-              metrics_namespace_project_environment_deployment_path(environment.project.namespace,
-                                                                    environment.project,
-                                                                    environment,
-                                                                    deployment)
+              metrics_project_environment_deployment_path(environment.project, environment, deployment)
             end
 
           {
             id: environment.id,
             name: environment.name,
-            url: namespace_project_environment_path(project.namespace, project, environment),
+            url: project_environment_path(project, environment),
             metrics_url: metrics_url,
             stop_url: stop_url,
             external_url: environment.external_url,
@@ -432,17 +238,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
 
   protected
 
-  def selected_target_project
-    if @project.id.to_s == params[:target_project_id] || @project.forked_project_link.nil?
-      @project
-    else
-      @project.forked_project_link.forked_from_project
-    end
-  end
-
-  def merge_request
-    @issuable = @merge_request ||= @project.merge_requests.find_by!(iid: params[:id])
-  end
   alias_method :subscribable_resource, :merge_request
   alias_method :issuable, :merge_request
   alias_method :awardable, :merge_request
@@ -455,16 +250,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
     return render_404 unless can?(current_user, :admin_merge_request, @merge_request)
   end
 
-  def authorize_can_resolve_conflicts!
-    @conflicts_list = MergeRequests::Conflicts::ListService.new(@merge_request)
-
-    return render_404 unless @conflicts_list.can_be_resolved_by?(current_user)
-  end
-
-  def module_enabled
-    return render_404 unless @project.feature_available?(:merge_requests, current_user)
-  end
-
   def validates_merge_request
     # Show git not found page
     # if there is no saved commits between source & target branch
@@ -474,141 +259,17 @@ class Projects::MergeRequestsController < Projects::ApplicationController
     end
   end
 
-  def define_show_vars
-    @noteable = @merge_request
-    @commits_count = @merge_request.commits_count
-
-    if @merge_request.locked_long_ago?
-      @merge_request.unlock_mr
-      @merge_request.close
-    end
-
-    labels
-    define_pipelines_vars
-  end
-
-  # Discussion tab data is rendered on html responses of actions
-  # :show, :diff, :commits, :builds. but not when request the data through AJAX
-  def define_discussion_vars
-    # Build a note object for comment form
-    @note = @project.notes.new(noteable: @merge_request)
-
-    @discussions = @merge_request.discussions
-    @notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes))
-  end
-
-  def define_diff_vars
-    @merge_request_diff =
-      if params[:diff_id]
-        @merge_request.merge_request_diffs.viewable.find(params[:diff_id])
-      else
-        @merge_request.merge_request_diff
-      end
-
-    @merge_request_diffs = @merge_request.merge_request_diffs.viewable.select_without_diff
-    @comparable_diffs = @merge_request_diffs.select { |diff| diff.id < @merge_request_diff.id }
-
-    if params[:start_sha].present?
-      @start_sha = params[:start_sha]
-      @start_version = @comparable_diffs.find { |diff| diff.head_commit_sha == @start_sha }
-
-      unless @start_version
-        @start_sha = @merge_request_diff.head_commit_sha
-        @start_version = @merge_request_diff
-      end
-    end
-
-    @compare =
-      if @start_sha
-        @merge_request_diff.compare_with(@start_sha)
-      else
-        @merge_request_diff
-      end
-
-    @diffs = @compare.diffs(diff_options)
-  end
-
-  def define_diff_comment_vars
-    @new_diff_note_attrs = {
-      noteable_type: 'MergeRequest',
-      noteable_id: @merge_request.id
-    }
-
-    @diff_notes_disabled = false
-
-    @use_legacy_diff_notes = !@merge_request.has_complete_diff_refs?
-
-    @grouped_diff_discussions = @merge_request.grouped_diff_discussions(@compare.diff_refs)
-    @notes = prepare_notes_for_rendering(@grouped_diff_discussions.values.flatten.flat_map(&:notes))
-  end
-
-  def define_pipelines_vars
-    @pipelines = @merge_request.all_pipelines
-    @pipeline = @merge_request.head_pipeline
-    @statuses_count = @pipeline.present? ? @pipeline.statuses.relevant.count : 0
-  end
-
-  def define_new_vars
-    @noteable = @merge_request
-
-    @target_branches = if @merge_request.target_project
-                         @merge_request.target_project.repository.branch_names
-                       else
-                         []
-                       end
-
-    @target_project = merge_request.target_project
-    @source_project = merge_request.source_project
-    @commits = @merge_request.compare_commits.reverse
-    @commit = @merge_request.diff_head_commit
-
-    @note_counts = Note.where(commit_id: @commits.map(&:id)).
-      group(:commit_id).count
-
-    @labels = LabelsFinder.new(current_user, project_id: @project.id).execute
-
-    @show_changes_tab = params[:show_changes].present?
-
-    define_pipelines_vars
-  end
-
   def invalid_mr
     # Render special view for MR with removed target branch
     render 'invalid'
   end
 
-  def merge_request_params
-    params.require(:merge_request)
-      .permit(merge_request_params_ce)
-  end
-
-  def merge_request_params_ce
-    [
-      :assignee_id,
-      :description,
-      :force_remove_source_branch,
-      :lock_version,
-      :milestone_id,
-      :source_branch,
-      :source_project_id,
-      :state_event,
-      :target_branch,
-      :target_project_id,
-      :task_num,
-      :title,
-
-      label_ids: []
-    ]
-  end
-
   def merge_params
-    params.permit(:should_remove_source_branch, :commit_message)
+    params.permit(merge_params_attributes)
   end
 
-  # Make sure merge requests created before 8.0
-  # have head file in refs/merge-requests/
-  def ensure_ref_fetched
-    @merge_request.ensure_ref_fetched
+  def merge_params_attributes
+    [:should_remove_source_branch, :commit_message]
   end
 
   def merge_when_pipeline_succeeds_active?
@@ -616,11 +277,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
       @merge_request.head_pipeline && @merge_request.head_pipeline.active?
   end
 
-  def build_merge_request
-    params[:merge_request] ||= ActionController::Parameters.new(source_project: @project)
-    @merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params.merge(diff_options: diff_options)).execute
-  end
-
   def close_merge_request_without_source_project
     if !@merge_request.source_project && @merge_request.open?
       @merge_request.close
@@ -648,7 +304,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
       return :failed unless @merge_request.head_pipeline
 
       if @merge_request.head_pipeline.active?
-        MergeRequests::MergeWhenPipelineSucceedsService
+        ::MergeRequests::MergeWhenPipelineSucceedsService
           .new(@project, current_user, merge_params)
           .execute(@merge_request)
 
@@ -672,4 +328,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
   def serializer
     MergeRequestSerializer.new(current_user: current_user, project: merge_request.project)
   end
+
+  def define_edit_vars
+    @source_project = @merge_request.source_project
+    @target_project = @merge_request.target_project
+    @target_branches = @merge_request.target_project.repository.branch_names
+  end
 end
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index ae16f69955a30183b5660a488830ca25726975f6..a80562e77ce05fd7a584cf4e6b30e651e14373c8 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -1,8 +1,8 @@
 class Projects::MilestonesController < Projects::ApplicationController
   include MilestoneActions
 
-  before_action :module_enabled
-  before_action :milestone, only: [:edit, :update, :destroy, :show, :sort_issues, :sort_merge_requests, :merge_requests, :participants, :labels]
+  before_action :check_issuables_available!
+  before_action :milestone, only: [:edit, :update, :destroy, :show, :merge_requests, :participants, :labels]
 
   # Allow read any milestone
   before_action :authorize_read_milestone!
@@ -51,8 +51,7 @@ class Projects::MilestonesController < Projects::ApplicationController
     @milestone = Milestones::CreateService.new(project, current_user, milestone_params).execute
 
     if @milestone.save
-      redirect_to namespace_project_milestone_path(@project.namespace,
-                                                   @project, @milestone)
+      redirect_to project_milestone_path(@project, @milestone)
     else
       render "new"
     end
@@ -65,8 +64,7 @@ class Projects::MilestonesController < Projects::ApplicationController
       format.js
       format.html do
         if @milestone.valid?
-          redirect_to namespace_project_milestone_path(@project.namespace,
-                                                   @project, @milestone)
+          redirect_to project_milestone_path(@project, @milestone)
         else
           render :edit
         end
@@ -85,22 +83,6 @@ class Projects::MilestonesController < Projects::ApplicationController
     end
   end
 
-  def sort_issues
-    @milestone.sort_issues(params['sortable_issue'].map(&:to_i))
-
-    render json: { saved: true }
-  end
-
-  def sort_merge_requests
-    @merge_requests = @milestone.merge_requests.where(id: params['sortable_merge_request'])
-    @merge_requests.each do |merge_request|
-      merge_request.position = params['sortable_merge_request'].index(merge_request.id.to_s) + 1
-      merge_request.save
-    end
-
-    render json: { saved: true }
-  end
-
   protected
 
   def milestone
@@ -111,12 +93,6 @@ class Projects::MilestonesController < Projects::ApplicationController
     return render_404 unless can?(current_user, :admin_milestone, @project)
   end
 
-  def module_enabled
-    unless @project.feature_available?(:issues, current_user) || @project.feature_available?(:merge_requests, current_user)
-      return render_404
-    end
-  end
-
   def milestone_params
     params.require(:milestone).permit(:title, :description, :start_date, :due_date, :state_event)
   end
diff --git a/app/controllers/projects/network_controller.rb b/app/controllers/projects/network_controller.rb
index 33a152ad34f86bdf2304a26e6afb089b4d112f1b..dfa5e4f7f46c9ebbe868e84c2c6dd18f6a7126f3 100644
--- a/app/controllers/projects/network_controller.rb
+++ b/app/controllers/projects/network_controller.rb
@@ -8,8 +8,8 @@ class Projects::NetworkController < Projects::ApplicationController
   before_action :assign_commit
 
   def show
-    @url = namespace_project_network_path(@project.namespace, @project, @ref, @options.merge(format: :json))
-    @commit_url = namespace_project_commit_path(@project.namespace, @project, 'ae45ca32').gsub("ae45ca32", "%s")
+    @url = project_network_path(@project, @ref, @options.merge(format: :json))
+    @commit_url = project_commit_path(@project, 'ae45ca32').gsub("ae45ca32", "%s")
 
     respond_to do |format|
       format.html do
diff --git a/app/controllers/projects/pages_controller.rb b/app/controllers/projects/pages_controller.rb
index 28b383e69ebcb48ecf26aefe6bc95b865448ed91..d421b1a8eb5c729a0040c72faf22c362d9083062 100644
--- a/app/controllers/projects/pages_controller.rb
+++ b/app/controllers/projects/pages_controller.rb
@@ -15,7 +15,7 @@ class Projects::PagesController < Projects::ApplicationController
 
     respond_to do |format|
       format.html  do
-        redirect_to namespace_project_pages_path(@project.namespace, @project),
+        redirect_to project_pages_path(@project),
                     status: 302,
                     notice: 'Pages were removed'
       end
diff --git a/app/controllers/projects/pages_domains_controller.rb b/app/controllers/projects/pages_domains_controller.rb
index dbd011f6c5d235946365d17354ae73f5d8535162..15e77d854dcfdcd88b862e96cc2deb1f129fb44c 100644
--- a/app/controllers/projects/pages_domains_controller.rb
+++ b/app/controllers/projects/pages_domains_controller.rb
@@ -16,7 +16,7 @@ class Projects::PagesDomainsController < Projects::ApplicationController
     @domain = @project.pages_domains.create(pages_domain_params)
 
     if @domain.valid?
-      redirect_to namespace_project_pages_path(@project.namespace, @project)
+      redirect_to project_pages_path(@project)
     else
       render 'new'
     end
@@ -27,7 +27,7 @@ class Projects::PagesDomainsController < Projects::ApplicationController
 
     respond_to do |format|
       format.html do
-        redirect_to namespace_project_pages_path(@project.namespace, @project),
+        redirect_to project_pages_path(@project),
                     status: 302,
                     notice: 'Domain was removed'
       end
diff --git a/app/controllers/projects/pipeline_schedules_controller.rb b/app/controllers/projects/pipeline_schedules_controller.rb
index ef4f083b98facf07accbf36983d601e846d46d02..0d967a7e691a40e61e2e3e0441f424aa8e396664 100644
--- a/app/controllers/projects/pipeline_schedules_controller.rb
+++ b/app/controllers/projects/pipeline_schedules_controller.rb
@@ -1,6 +1,7 @@
 class Projects::PipelineSchedulesController < Projects::ApplicationController
   before_action :authorize_read_pipeline_schedule!
-  before_action :authorize_create_pipeline_schedule!, only: [:new, :create, :edit, :take_ownership, :update]
+  before_action :authorize_create_pipeline_schedule!, only: [:new, :create]
+  before_action :authorize_update_pipeline_schedule!, only: [:edit, :take_ownership, :update]
   before_action :authorize_admin_pipeline_schedule!, only: [:destroy]
 
   before_action :schedule, only: [:edit, :update, :destroy, :take_ownership]
@@ -33,7 +34,7 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
 
   def update
     if schedule.update(schedule_params)
-      redirect_to namespace_project_pipeline_schedules_path(@project.namespace.becomes(Namespace), @project)
+      redirect_to project_pipeline_schedules_path(@project)
     else
       render :edit
     end
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index 8effb792689c9cc114e9f399860c18562f8c64b6..a3bfbf0694e27352fbbdebaffe421d5ab4e6299c 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -60,7 +60,7 @@ class Projects::PipelinesController < Projects::ApplicationController
       .execute(:web, ignore_skip_ci: true, save_on_errors: false)
 
     if @pipeline.persisted?
-      redirect_to namespace_project_pipeline_path(project.namespace, project, @pipeline)
+      redirect_to project_pipeline_path(project, @pipeline)
     else
       render 'new'
     end
@@ -111,7 +111,7 @@ class Projects::PipelinesController < Projects::ApplicationController
 
     respond_to do |format|
       format.html do
-        redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project)
+        redirect_back_or_default default: project_pipelines_path(project)
       end
 
       format.json { head :no_content }
@@ -123,7 +123,7 @@ class Projects::PipelinesController < Projects::ApplicationController
 
     respond_to do |format|
       format.html do
-        redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project)
+        redirect_back_or_default default: project_pipelines_path(project)
       end
 
       format.json { head :no_content }
@@ -135,7 +135,12 @@ class Projects::PipelinesController < Projects::ApplicationController
     @charts[:week] = Ci::Charts::WeekChart.new(project)
     @charts[:month] = Ci::Charts::MonthChart.new(project)
     @charts[:year] = Ci::Charts::YearChart.new(project)
-    @charts[:build_times] = Ci::Charts::BuildTime.new(project)
+    @charts[:pipeline_times] = Ci::Charts::PipelineTime.new(project)
+
+    @counts = {}
+    @counts[:total] = @project.pipelines.count(:all)
+    @counts[:success] = @project.pipelines.success.count(:all)
+    @counts[:failed] = @project.pipelines.failed.count(:all)
   end
 
   private
diff --git a/app/controllers/projects/pipelines_settings_controller.rb b/app/controllers/projects/pipelines_settings_controller.rb
index 38a476510008cea47a704714911756d22b7c47a8..9d24ebe2138b69125228d97ae3ddc09360f56af0 100644
--- a/app/controllers/projects/pipelines_settings_controller.rb
+++ b/app/controllers/projects/pipelines_settings_controller.rb
@@ -2,13 +2,13 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
   before_action :authorize_admin_pipeline!
 
   def show
-    redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project, params: params)
+    redirect_to project_settings_ci_cd_path(@project, params: params)
   end
 
   def update
     if @project.update_attributes(update_params)
       flash[:notice] = "Pipelines settings for '#{@project.name}' were successfully updated."
-      redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
+      redirect_to project_settings_ci_cd_path(@project)
     else
       render 'show'
     end
@@ -23,7 +23,7 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
   def update_params
     params.require(:project).permit(
       :runners_token, :builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
-      :public_builds, :auto_cancel_pending_pipelines
+      :public_builds, :auto_cancel_pending_pipelines, :ci_config_path
     )
   end
 end
diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
index d2d267385828a5012f84a8452e919889f4cf7c08..57a6686f66c83e3c2334f003da2387041634fae4 100644
--- a/app/controllers/projects/project_members_controller.rb
+++ b/app/controllers/projects/project_members_controller.rb
@@ -7,7 +7,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
 
   def index
     sort = params[:sort].presence || sort_value_name
-    redirect_to namespace_project_settings_members_path(@project.namespace, @project, sort: sort)
+    redirect_to project_settings_members_path(@project, sort: sort)
   end
 
   def update
@@ -19,7 +19,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
   end
 
   def resend_invite
-    redirect_path = namespace_project_settings_members_path(@project.namespace, @project)
+    redirect_path = project_settings_members_path(@project)
 
     @project_member = @project.project_members.find(params[:id])
 
@@ -42,7 +42,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
       return render_404
     end
 
-    redirect_to(namespace_project_settings_members_path(project.namespace, project),
+    redirect_to(project_settings_members_path(project),
                 notice: notice)
   end
 
diff --git a/app/controllers/projects/prometheus_controller.rb b/app/controllers/projects/prometheus_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..507468d71023c3a384b38e3cc7183f1df7d67fce
--- /dev/null
+++ b/app/controllers/projects/prometheus_controller.rb
@@ -0,0 +1,24 @@
+class Projects::PrometheusController < Projects::ApplicationController
+  before_action :authorize_read_project!
+  before_action :require_prometheus_metrics!
+
+  def active_metrics
+    respond_to do |format|
+      format.json do
+        matched_metrics = project.prometheus_service.matched_metrics || {}
+
+        if matched_metrics.any?
+          render json: matched_metrics
+        else
+          head :no_content
+        end
+      end
+    end
+  end
+
+  private
+
+  def require_prometheus_metrics!
+    render_404 unless project.prometheus_service.present?
+  end
+end
diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb
index 2a0b58fae7c4e28a9cf6a28b0f35fc797e0eaf4c..1eb78d8b5223c6ba7249c2aebd834d64832e87c3 100644
--- a/app/controllers/projects/refs_controller.rb
+++ b/app/controllers/projects/refs_controller.rb
@@ -13,21 +13,21 @@ class Projects::RefsController < Projects::ApplicationController
         new_path =
           case params[:destination]
           when "tree"
-            namespace_project_tree_path(@project.namespace, @project, @id)
+            project_tree_path(@project, @id)
           when "blob"
-            namespace_project_blob_path(@project.namespace, @project, @id)
+            project_blob_path(@project, @id)
           when "graph"
-            namespace_project_network_path(@project.namespace, @project, @id, @options)
+            project_network_path(@project, @id, @options)
           when "graphs"
-            namespace_project_graph_path(@project.namespace, @project, @id)
+            project_graph_path(@project, @id)
           when "find_file"
-            namespace_project_find_file_path(@project.namespace, @project, @id)
+            project_find_file_path(@project, @id)
           when "graphs_commits"
-            commits_namespace_project_graph_path(@project.namespace, @project, @id)
+            commits_project_graph_path(@project, @id)
           when "badges"
-            namespace_project_pipelines_settings_path(@project.namespace, @project, ref: @id)
+            project_pipelines_settings_path(@project, ref: @id)
           else
-            namespace_project_commits_path(@project.namespace, @project, @id)
+            project_commits_path(@project, @id)
           end
 
         redirect_to new_path
@@ -62,7 +62,7 @@ class Projects::RefsController < Projects::ApplicationController
 
     offset = (@offset + @limit)
     if contents.size > offset
-      @more_log_url = logs_file_namespace_project_ref_path(@project.namespace, @project, @ref, @path || '', offset: offset)
+      @more_log_url = logs_file_project_ref_path(@project, @ref, @path || '', offset: offset)
     end
 
     respond_to do |format|
diff --git a/app/controllers/projects/registry/repositories_controller.rb b/app/controllers/projects/registry/repositories_controller.rb
index 98e78585be888fe5c23cdabe04f05bd7880b9869..71e7dc70a4dea44794b075b69da95b38b583814b 100644
--- a/app/controllers/projects/registry/repositories_controller.rb
+++ b/app/controllers/projects/registry/repositories_controller.rb
@@ -10,11 +10,11 @@ module Projects
 
       def destroy
         if image.destroy
-          redirect_to project_container_registry_path(@project),
+          redirect_to project_container_registry_index_path(@project),
                       status: 302,
                       notice: 'Image repository has been removed successfully!'
         else
-          redirect_to project_container_registry_path(@project),
+          redirect_to project_container_registry_index_path(@project),
                       status: 302,
                       alert: 'Failed to remove image repository!'
         end
diff --git a/app/controllers/projects/registry/tags_controller.rb b/app/controllers/projects/registry/tags_controller.rb
index 5050dba3aab6441686ea7082afe058ca1a1ed946..ae72bd03cfba637884fe8046cc718ee5f15bfc42 100644
--- a/app/controllers/projects/registry/tags_controller.rb
+++ b/app/controllers/projects/registry/tags_controller.rb
@@ -5,11 +5,11 @@ module Projects
 
       def destroy
         if tag.delete
-          redirect_to project_container_registry_path(@project),
+          redirect_to project_container_registry_index_path(@project),
                       status: 302,
                       notice: 'Registry tag has been removed successfully!'
         else
-          redirect_to project_container_registry_path(@project),
+          redirect_to project_container_registry_index_path(@project),
                       status: 302,
                       alert: 'Failed to remove registry tag!'
         end
diff --git a/app/controllers/projects/releases_controller.rb b/app/controllers/projects/releases_controller.rb
index 2c097cb4d8d7c622007db8f2d73be16e95137e97..3e0a530fdb9b25f89cd306454180c271d8bb4a23 100644
--- a/app/controllers/projects/releases_controller.rb
+++ b/app/controllers/projects/releases_controller.rb
@@ -19,7 +19,7 @@ class Projects::ReleasesController < Projects::ApplicationController
       release.destroy
     end
 
-    redirect_to namespace_project_tag_path(@project.namespace, @project, @tag.name)
+    redirect_to project_tag_path(@project, @tag.name)
   end
 
   private
diff --git a/app/controllers/projects/runners_controller.rb b/app/controllers/projects/runners_controller.rb
index 160e632648a96746c4b1d57e2e9c09ec63a65a6f..9f9773575a5f836fd49ac7a9a2cb024db1a5e966 100644
--- a/app/controllers/projects/runners_controller.rb
+++ b/app/controllers/projects/runners_controller.rb
@@ -5,7 +5,7 @@ class Projects::RunnersController < Projects::ApplicationController
   layout 'project_settings'
 
   def index
-    redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
+    redirect_to project_settings_ci_cd_path(@project)
   end
 
   def edit
@@ -49,7 +49,7 @@ class Projects::RunnersController < Projects::ApplicationController
   def toggle_shared_runners
     project.toggle!(:shared_runners_enabled)
 
-    redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
+    redirect_to project_settings_ci_cd_path(@project)
   end
 
   protected
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index 704f8cc8a799eec167aabca7ee69fdf8eaa10301..d54a1111f115a138709fcda4438cb5cda80df411 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -15,7 +15,7 @@ class Projects::ServicesController < Projects::ApplicationController
 
   def update
     if @service.save(context: :manual_change)
-      redirect_to(namespace_project_settings_integrations_path(@project.namespace, @project), notice: success_message)
+      redirect_to(project_settings_integrations_path(@project), notice: success_message)
     else
       render 'edit'
     end
diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb
index 6f009d6195062cba1af675340f2354b6d9dd113e..24fe78bc1bd0d62bd062c35fed90a3e8b8d32a82 100644
--- a/app/controllers/projects/settings/ci_cd_controller.rb
+++ b/app/controllers/projects/settings/ci_cd_controller.rb
@@ -14,8 +14,8 @@ module Projects
 
       def define_runners_variables
         @project_runners = @project.runners.ordered
-        @assignable_runners = current_user.ci_authorized_runners.
-          assignable_for(project).ordered.page(params[:page]).per(20)
+        @assignable_runners = current_user.ci_authorized_runners
+          .assignable_for(project).ordered.page(params[:page]).per(20)
         @shared_runners = Ci::Runner.shared.active
         @shared_runners_count = @shared_runners.count(:all)
       end
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index 8a8f8d6a27d2630c09208637f0af9363f0976ef6..d07143d294f70e6889c57b6b139102d0b11f85b7 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -5,7 +5,7 @@ class Projects::SnippetsController < Projects::ApplicationController
   include SnippetsActions
   include RendersBlob
 
-  before_action :module_enabled
+  before_action :check_snippets_available!
   before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam]
 
   # Allow read any snippet
@@ -30,7 +30,7 @@ class Projects::SnippetsController < Projects::ApplicationController
     ).execute
     @snippets = @snippets.page(params[:page])
     if @snippets.out_of_range? && @snippets.total_pages != 0
-      redirect_to namespace_project_snippets_path(page: @snippets.total_pages)
+      redirect_to project_snippets_path(@project, page: @snippets.total_pages)
     end
   end
 
@@ -79,7 +79,7 @@ class Projects::SnippetsController < Projects::ApplicationController
 
     @snippet.destroy
 
-    redirect_to namespace_project_snippets_path(@project.namespace, @project), status: 302
+    redirect_to project_snippets_path(@project), status: 302
   end
 
   protected
@@ -90,6 +90,10 @@ class Projects::SnippetsController < Projects::ApplicationController
   alias_method :awardable, :snippet
   alias_method :spammable, :snippet
 
+  def spammable_path
+    project_snippet_path(@project, @snippet)
+  end
+
   def authorize_read_project_snippet!
     return render_404 unless can?(current_user, :read_project_snippet, @snippet)
   end
@@ -102,10 +106,6 @@ class Projects::SnippetsController < Projects::ApplicationController
     return render_404 unless can?(current_user, :admin_project_snippet, @snippet)
   end
 
-  def module_enabled
-    return render_404 unless @project.feature_available?(:snippets, current_user)
-  end
-
   def snippet_params
     params.require(:project_snippet).permit(:title, :content, :file_name, :private, :visibility_level, :description)
   end
diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb
index afbea3e2b40e748f90dd83e7e7cdad67ea75dcec..b62d7d9b7c5392abd4fe8be86342583de5f05843 100644
--- a/app/controllers/projects/tags_controller.rb
+++ b/app/controllers/projects/tags_controller.rb
@@ -29,13 +29,13 @@ class Projects::TagsController < Projects::ApplicationController
   end
 
   def create
-    result = Tags::CreateService.new(@project, current_user).
-      execute(params[:tag_name], params[:ref], params[:message], params[:release_description])
+    result = Tags::CreateService.new(@project, current_user)
+      .execute(params[:tag_name], params[:ref], params[:message], params[:release_description])
 
     if result[:status] == :success
       @tag = result[:tag]
 
-      redirect_to namespace_project_tag_path(@project.namespace, @project, @tag.name)
+      redirect_to project_tag_path(@project, @tag.name)
     else
       @error = result[:message]
       @message = params[:message]
@@ -50,7 +50,7 @@ class Projects::TagsController < Projects::ApplicationController
     respond_to do |format|
       if result[:status] == :success
         format.html do
-          redirect_to namespace_project_tags_path(@project.namespace, @project), status: 303
+          redirect_to project_tags_path(@project), status: 303
         end
 
         format.js
@@ -58,7 +58,7 @@ class Projects::TagsController < Projects::ApplicationController
         @error = result[:message]
 
         format.html do
-          redirect_to namespace_project_tags_path(@project.namespace, @project),
+          redirect_to project_tags_path(@project),
             alert: @error, status: 303
         end
 
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index 266a15c1cf945589e7e4be328f34a5d1223004f7..30181ac3bdf6ac743904e9354eae61f5799f9eb7 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -16,7 +16,7 @@ class Projects::TreeController < Projects::ApplicationController
     if tree.entries.empty?
       if @repository.blob_at(@commit.id, @path)
         return redirect_to(
-          namespace_project_blob_path(@project.namespace, @project,
+          project_blob_path(@project,
                                       File.join(@ref, @path))
         )
       elsif @path.present?
@@ -37,8 +37,8 @@ class Projects::TreeController < Projects::ApplicationController
     return render_404 unless @commit_params.values.all?
 
     create_commit(Files::CreateDirService,  success_notice: "The directory has been successfully created.",
-                                            success_path: namespace_project_tree_path(@project.namespace, @project, File.join(@branch_name, @dir_name)),
-                                            failure_path: namespace_project_tree_path(@project.namespace, @project, @ref))
+                                            success_path: project_tree_path(@project, File.join(@branch_name, @dir_name)),
+                                            failure_path: project_tree_path(@project, @ref))
   end
 
   private
diff --git a/app/controllers/projects/triggers_controller.rb b/app/controllers/projects/triggers_controller.rb
index e86adddd77fc18dbf2f070d950c7c2802931155f..a5b17fa65ea3abb07bb92a0606503e49365e8519 100644
--- a/app/controllers/projects/triggers_controller.rb
+++ b/app/controllers/projects/triggers_controller.rb
@@ -7,7 +7,7 @@ class Projects::TriggersController < Projects::ApplicationController
   layout 'project_settings'
 
   def index
-    redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
+    redirect_to project_settings_ci_cd_path(@project)
   end
 
   def create
@@ -19,7 +19,7 @@ class Projects::TriggersController < Projects::ApplicationController
       flash[:alert] = 'You could not create a new trigger.'
     end
 
-    redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
+    redirect_to project_settings_ci_cd_path(@project)
   end
 
   def take_ownership
@@ -29,7 +29,7 @@ class Projects::TriggersController < Projects::ApplicationController
       flash[:alert] = 'You could not take ownership of trigger.'
     end
 
-    redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
+    redirect_to project_settings_ci_cd_path(@project)
   end
 
   def edit
@@ -37,7 +37,7 @@ class Projects::TriggersController < Projects::ApplicationController
 
   def update
     if trigger.update(trigger_params)
-      redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project), notice: 'Trigger was successfully updated.'
+      redirect_to project_settings_ci_cd_path(@project), notice: 'Trigger was successfully updated.'
     else
       render action: "edit"
     end
@@ -50,7 +50,7 @@ class Projects::TriggersController < Projects::ApplicationController
       flash[:alert] = "Could not remove the trigger."
     end
 
-    redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project), status: 302
+    redirect_to project_settings_ci_cd_path(@project), status: 302
   end
 
   private
diff --git a/app/controllers/projects/variables_controller.rb b/app/controllers/projects/variables_controller.rb
index 50e25a00f03215a5d3e4eb7040ccdc02a5afb23f..573d1c0562283f99f5ed8db30f6687b011da80f3 100644
--- a/app/controllers/projects/variables_controller.rb
+++ b/app/controllers/projects/variables_controller.rb
@@ -4,7 +4,7 @@ class Projects::VariablesController < Projects::ApplicationController
   layout 'project_settings'
 
   def index
-    redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
+    redirect_to project_settings_ci_cd_path(@project)
   end
 
   def show
@@ -15,7 +15,7 @@ class Projects::VariablesController < Projects::ApplicationController
     @variable = @project.variables.find(params[:id])
 
     if @variable.update_attributes(project_params)
-      redirect_to namespace_project_variables_path(project.namespace, project), notice: 'Variable was successfully updated.'
+      redirect_to project_variables_path(project), notice: 'Variable was successfully updated.'
     else
       render action: "show"
     end
@@ -26,7 +26,7 @@ class Projects::VariablesController < Projects::ApplicationController
 
     if @variable.valid? && @project.variables << @variable
       flash[:notice] = 'Variables were successfully updated.'
-      redirect_to namespace_project_settings_ci_cd_path(project.namespace, project)
+      redirect_to project_settings_ci_cd_path(project)
     else
       render "show"
     end
@@ -36,7 +36,7 @@ class Projects::VariablesController < Projects::ApplicationController
     @key = @project.variables.find(params[:id])
     @key.destroy
 
-    redirect_to namespace_project_settings_ci_cd_path(project.namespace, project),
+    redirect_to project_settings_ci_cd_path(project),
                 status: 302,
                 notice: 'Variable was successfully removed.'
   end
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index e54b90b8d52d22bf6684a585d32516eab265e397..ac98470c2b105192d3634c45d2d231fd9e940078 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -49,7 +49,7 @@ class Projects::WikisController < Projects::ApplicationController
 
     if @page.valid?
       redirect_to(
-        namespace_project_wiki_path(@project.namespace, @project, @page),
+        project_wiki_path(@project, @page),
         notice: 'Wiki was successfully updated.'
       )
     else
@@ -62,7 +62,7 @@ class Projects::WikisController < Projects::ApplicationController
 
     if @page.persisted?
       redirect_to(
-        namespace_project_wiki_path(@project.namespace, @project, @page),
+        project_wiki_path(@project, @page),
         notice: 'Wiki was successfully updated.'
       )
     else
@@ -75,7 +75,7 @@ class Projects::WikisController < Projects::ApplicationController
 
     unless @page
       redirect_to(
-        namespace_project_wiki_path(@project.namespace, @project, :home),
+        project_wiki_path(@project, :home),
         notice: "Page not found"
       )
     end
@@ -85,7 +85,7 @@ class Projects::WikisController < Projects::ApplicationController
     @page = @project_wiki.find_page(params[:id])
     WikiPages::DestroyService.new(@project, current_user).execute(@page)
 
-    redirect_to namespace_project_wiki_path(@project.namespace, @project, :home),
+    redirect_to project_wiki_path(@project, :home),
                 status: 302,
                 notice: "Page was successfully deleted"
   end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 5480814874b8266f4ca7f9be299c8c539333371b..87a69e8e6f9d70de0b295eec0ad77ae7e01a48e8 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -92,12 +92,12 @@ class ProjectsController < Projects::ApplicationController
 
   def show
     if @project.import_in_progress?
-      redirect_to namespace_project_import_path(@project.namespace, @project)
+      redirect_to project_import_path(@project)
       return
     end
 
     if @project.pending_delete?
-      flash[:alert] = _("Project '%{project_name}' queued for deletion.") % { project_name: @project.name }
+      flash.now[:alert] = _("Project '%{project_name}' queued for deletion.") % { project_name: @project.name }
     end
 
     respond_to do |format|
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index 4a5796017851ccdeb46d54777dbd52de0bd532e8..d58c8d14a75aca7b651b6f09bb0615609ce887d5 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -44,7 +44,7 @@ class SearchController < ApplicationController
       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
+      redirect_to project_commit_path(@project, only_commit) if found_by_commit_sha
     end
   end
 end
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index d7c702b94f8157d560fad7be8f3443935f3ce00a..f39441a281e99c52c874790d709413eefe16ae5a 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -60,10 +60,11 @@ class SessionsController < Devise::SessionsController
 
     return unless user && user.require_password?
 
-    token = user.generate_reset_token
-    user.save
+    Users::UpdateService.new(user).execute do |user|
+      @token = user.generate_reset_token
+    end
 
-    redirect_to edit_user_password_path(reset_password_token: token),
+    redirect_to edit_user_password_path(reset_password_token: @token),
       notice: "Please create a password for your new account."
   end
 
@@ -128,8 +129,8 @@ class SessionsController < Devise::SessionsController
   end
 
   def log_audit_event(user, options = {})
-    AuditEventService.new(user, user, options).
-      for_authentication.security_event
+    AuditEventService.new(user, user, options)
+      .for_authentication.security_event
   end
 
   def log_user_activity(user)
diff --git a/app/controllers/sherlock/application_controller.rb b/app/controllers/sherlock/application_controller.rb
index 682ca5e382155f51ccc84432c29b9ecb13ea296a..6bdd3568a7899d2bd2497363807dc32565a095bc 100644
--- a/app/controllers/sherlock/application_controller.rb
+++ b/app/controllers/sherlock/application_controller.rb
@@ -4,8 +4,8 @@ module Sherlock
 
     def find_transaction
       if params[:transaction_id]
-        @transaction = Gitlab::Sherlock.collection.
-          find_transaction(params[:transaction_id])
+        @transaction = Gitlab::Sherlock.collection
+          .find_transaction(params[:transaction_id])
       end
     end
   end
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index 3d86dd2ea2c0c23c0bdbca590259447bb9a3366c..8c3abd0a085c5c3740b35b959a0b69d2e73c3a51 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -107,6 +107,10 @@ class SnippetsController < ApplicationController
   alias_method :awardable, :snippet
   alias_method :spammable, :snippet
 
+  def spammable_path
+    snippet_path(@snippet)
+  end
+
   def authorize_read_snippet!
     return if can?(current_user, :read_personal_snippet, @snippet)
 
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index c211106fbaa07d86a98d0d3d14a7fac29168e346..8131eba6a2fd3e82e5f3ce84c06ac84868f65f81 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -106,11 +106,11 @@ class UsersController < ApplicationController
 
   def load_events
     # Get user activity feed for projects common for both users
-    @events = user.recent_events.
-      merge(projects_for_current_user).
-      references(:project).
-      with_associations.
-      limit_recent(20, params[:offset])
+    @events = user.recent_events
+      .merge(projects_for_current_user)
+      .references(:project)
+      .with_associations
+      .limit_recent(20, params[:offset])
   end
 
   def load_projects
diff --git a/app/finders/events_finder.rb b/app/finders/events_finder.rb
index b0450ddc1fdb1152ac9c08c88f7156cc40369efc..46ecbaba73af4c8c00f3ef1294eaec9c6b2e7ded 100644
--- a/app/finders/events_finder.rb
+++ b/app/finders/events_finder.rb
@@ -33,7 +33,8 @@ class EventsFinder
   private
 
   def by_current_user_access(events)
-    events.merge(ProjectsFinder.new(current_user: current_user).execute).references(:project)
+    events.merge(ProjectsFinder.new(current_user: current_user).execute)
+      .joins(:project)
   end
 
   def by_action(events)
diff --git a/app/finders/group_members_finder.rb b/app/finders/group_members_finder.rb
index fce3775f40e57fc51f1124bff256e0f3128738c8..067aff408dfa7d0805b8edfd288027e02e1acf26 100644
--- a/app/finders/group_members_finder.rb
+++ b/app/finders/group_members_finder.rb
@@ -8,9 +8,9 @@ class GroupMembersFinder
 
     return group_members unless @group.parent
 
-    parents_members = GroupMember.non_request.
-      where(source_id: @group.ancestors.select(:id)).
-      where.not(user_id: @group.users.select(:id))
+    parents_members = GroupMember.non_request
+      .where(source_id: @group.ancestors.select(:id))
+      .where.not(user_id: @group.users.select(:id))
 
     wheres = ["members.id IN (#{group_members.select(:id).to_sql})"]
     wheres << "members.id IN (#{parents_members.select(:id).to_sql})"
diff --git a/app/finders/group_projects_finder.rb b/app/finders/group_projects_finder.rb
index f043c38c6f9d878eec2e46faefe55cd149aea237..f2d3b90b8e260388e75bdae1bfdf1f2d2599b3cb 100644
--- a/app/finders/group_projects_finder.rb
+++ b/app/finders/group_projects_finder.rb
@@ -29,35 +29,69 @@ class GroupProjectsFinder < ProjectsFinder
   private
 
   def init_collection
-    only_owned  = options.fetch(:only_owned, false)
-    only_shared = options.fetch(:only_shared, false)
+    projects = if current_user
+                 collection_with_user
+               else
+                 collection_without_user
+               end
 
-    projects = []
+    union(projects)
+  end
 
-    if current_user
-      if group.users.include?(current_user)
-        projects << group.projects unless only_shared
-        projects << group.shared_projects unless only_owned
+  def collection_with_user
+    if group.users.include?(current_user)
+      if only_shared?
+        [shared_projects]
+      elsif only_owned?
+        [owned_projects]
       else
-        unless only_shared
-          projects << group.projects.visible_to_user(current_user)
-          projects << group.projects.public_to_user(current_user)
-        end
-
-        unless only_owned
-          projects << group.shared_projects.visible_to_user(current_user)
-          projects << group.shared_projects.public_to_user(current_user)
-        end
+        [shared_projects, owned_projects]
       end
     else
-      projects << group.projects.public_only unless only_shared
-      projects << group.shared_projects.public_only unless only_owned
+      if only_shared?
+        [shared_projects.public_or_visible_to_user(current_user)]
+      elsif only_owned?
+        [owned_projects.public_or_visible_to_user(current_user)]
+      else
+        [
+          owned_projects.public_or_visible_to_user(current_user),
+          shared_projects.public_or_visible_to_user(current_user)
+        ]
+      end
     end
+  end
 
-    projects
+  def collection_without_user
+    if only_shared?
+      [shared_projects.public_only]
+    elsif only_owned?
+      [owned_projects.public_only]
+    else
+      [shared_projects.public_only, owned_projects.public_only]
+    end
   end
 
   def union(items)
-    find_union(items, Project)
+    if items.one?
+      items.first
+    else
+      find_union(items, Project)
+    end
+  end
+
+  def only_owned?
+    options.fetch(:only_owned, false)
+  end
+
+  def only_shared?
+    options.fetch(:only_shared, false)
+  end
+
+  def owned_projects
+    group.projects
+  end
+
+  def shared_projects
+    group.shared_projects
   end
 end
diff --git a/app/finders/groups_finder.rb b/app/finders/groups_finder.rb
index f68610e197cc6d6545520940bace1bc313bcae3a..e6fb112e7f282ce6fad43a7cfad901709efb2d2f 100644
--- a/app/finders/groups_finder.rb
+++ b/app/finders/groups_finder.rb
@@ -5,8 +5,10 @@ class GroupsFinder < UnionFinder
   end
 
   def execute
-    groups = find_union(all_groups, Group).with_route.order_id_desc
-    by_parent(groups)
+    items = all_groups.map do |item|
+      by_parent(item)
+    end
+    find_union(items, Group).with_route.order_id_desc
   end
 
   private
@@ -16,12 +18,22 @@ class GroupsFinder < UnionFinder
   def all_groups
     groups = []
 
-    groups << current_user.authorized_groups if current_user
+    if current_user
+      groups << Gitlab::GroupHierarchy.new(groups_for_ancestors, groups_for_descendants).all_groups
+    end
     groups << Group.unscoped.public_to_user(current_user)
 
     groups
   end
 
+  def groups_for_ancestors
+    current_user.authorized_groups
+  end
+
+  def groups_for_descendants
+    current_user.groups
+  end
+
   def by_parent(groups)
     return groups unless params[:parent]
 
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 957ad87585837e4e8268e50323c4b91ab8be843b..7bc2117f61eff617f97dc4a216fba3663165efa0 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -20,6 +20,7 @@
 #
 class IssuableFinder
   NONE = '0'.freeze
+  IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page].freeze
 
   attr_accessor :current_user, :params
 
@@ -41,6 +42,7 @@ class IssuableFinder
     items = by_iids(items)
     items = by_milestone(items)
     items = by_label(items)
+    items = by_created_at(items)
 
     # Filtering by project HAS TO be the last because we use the project IDs yielded by the issuable query thus far
     items = by_project(items)
@@ -61,7 +63,7 @@ class IssuableFinder
   # grouping and counting within that query.
   #
   def count_by_state
-    count_params = params.merge(state: nil, sort: nil)
+    count_params = params.merge(state: nil, sort: nil, for_counting: true)
     labels_count = label_names.any? ? label_names.count : 1
     finder = self.class.new(current_user, count_params)
     counts = Hash.new(0)
@@ -85,6 +87,10 @@ class IssuableFinder
     execute.find_by!(*params)
   end
 
+  def state_counter_cache_key(state)
+    Digest::SHA1.hexdigest(state_counter_cache_key_components(state).flatten.join('-'))
+  end
+
   def group
     return @group if defined?(@group)
 
@@ -402,7 +408,28 @@ class IssuableFinder
     params[:non_archived].present? ? items.non_archived : items
   end
 
+  def by_created_at(items)
+    if params[:created_after].present?
+      items = items.where(items.klass.arel_table[:created_at].gteq(params[:created_after]))
+    end
+
+    if params[:created_before].present?
+      items = items.where(items.klass.arel_table[:created_at].lteq(params[:created_before]))
+    end
+
+    items
+  end
+
   def current_user_related?
     params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me'
   end
+
+  def state_counter_cache_key_components(state)
+    opts = params.with_indifferent_access
+    opts[:state] = state
+    opts.except!(*IRRELEVANT_PARAMS_FOR_CACHE_KEY)
+    opts.delete_if { |_, value| value.blank? }
+
+    ['issuables_count', klass.to_ability_name, opts.sort]
+  end
 end
diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb
index b4c074bc69c94e7ffa20eb179b39a8e50c83b029..85230ff1293cbbb7038acf92e6f507d12b52f072 100644
--- a/app/finders/issues_finder.rb
+++ b/app/finders/issues_finder.rb
@@ -16,14 +16,72 @@
 #     sort: string
 #
 class IssuesFinder < IssuableFinder
+  CONFIDENTIAL_ACCESS_LEVEL = Gitlab::Access::REPORTER
+
   def klass
     Issue
   end
 
+  def with_confidentiality_access_check
+    return Issue.all if user_can_see_all_confidential_issues?
+    return Issue.where('issues.confidential IS NOT TRUE') if user_cannot_see_confidential_issues?
+
+    Issue.where('
+      issues.confidential IS NOT TRUE
+      OR (issues.confidential = TRUE
+        AND (issues.author_id = :user_id
+          OR EXISTS (SELECT TRUE FROM issue_assignees WHERE user_id = :user_id AND issue_id = issues.id)
+          OR issues.project_id IN(:project_ids)))',
+      user_id: current_user.id,
+      project_ids: current_user.authorized_projects(CONFIDENTIAL_ACCESS_LEVEL).select(:id))
+  end
+
   private
 
   def init_collection
-    IssuesFinder.not_restricted_by_confidentiality(current_user)
+    with_confidentiality_access_check
+  end
+
+  def user_can_see_all_confidential_issues?
+    return @user_can_see_all_confidential_issues if defined?(@user_can_see_all_confidential_issues)
+
+    return @user_can_see_all_confidential_issues = false if current_user.blank?
+    return @user_can_see_all_confidential_issues = true if current_user.full_private_access?
+
+    @user_can_see_all_confidential_issues =
+      project? &&
+      project &&
+      project.team.max_member_access(current_user.id) >= CONFIDENTIAL_ACCESS_LEVEL
+  end
+
+  # Anonymous users can't see any confidential issues.
+  #
+  # Users without access to see _all_ confidential issues (as in
+  # `user_can_see_all_confidential_issues?`) are more complicated, because they
+  # can see confidential issues where:
+  # 1. They are an assignee.
+  # 2. They are an author.
+  #
+  # That's fine for most cases, but if we're just counting, we need to cache
+  # effectively. If we cached this accurately, we'd have a cache key for every
+  # authenticated user without sufficient access to the project. Instead, when
+  # we are counting, we treat them as if they can't see any confidential issues.
+  #
+  # This does mean the counts may be wrong for those users, but avoids an
+  # explosion in cache keys.
+  def user_cannot_see_confidential_issues?(for_counting: false)
+    return false if user_can_see_all_confidential_issues?
+
+    current_user.blank? || for_counting || params[:for_counting]
+  end
+
+  def state_counter_cache_key_components(state)
+    extra_components = [
+      user_can_see_all_confidential_issues?,
+      user_cannot_see_confidential_issues?(for_counting: true)
+    ]
+
+    super + extra_components
   end
 
   def by_assignee(items)
@@ -38,21 +96,6 @@ class IssuesFinder < IssuableFinder
     end
   end
 
-  def self.not_restricted_by_confidentiality(user)
-    return Issue.where('issues.confidential IS NOT TRUE') if user.blank?
-
-    return Issue.all if user.admin?
-
-    Issue.where('
-      issues.confidential IS NOT TRUE
-      OR (issues.confidential = TRUE
-        AND (issues.author_id = :user_id
-          OR EXISTS (SELECT TRUE FROM issue_assignees WHERE user_id = :user_id AND issue_id = issues.id)
-          OR issues.project_id IN(:project_ids)))',
-      user_id: user.id,
-      project_ids: user.authorized_projects(Gitlab::Access::REPORTER).select(:id))
-  end
-
   def item_project_ids(items)
     items&.reorder(nil)&.select(:project_id)
   end
diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb
index 042d792dada7c26356db464b42317e3131b4aa2c..ce432ddbfe621c84fe35e55b80ed40238f11fae5 100644
--- a/app/finders/labels_finder.rb
+++ b/app/finders/labels_finder.rb
@@ -83,7 +83,12 @@ class LabelsFinder < UnionFinder
   def projects
     return @projects if defined?(@projects)
 
-    @projects = skip_authorization ? Project.all : ProjectsFinder.new(current_user: current_user).execute
+    @projects = if skip_authorization
+                  Project.all
+                else
+                  ProjectsFinder.new(params: { non_archived: true }, current_user: current_user).execute
+                end
+
     @projects = @projects.in_namespace(params[:group_id]) if group?
     @projects = @projects.where(id: params[:project_ids]) if projects?
     @projects = @projects.reorder(nil)
diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb
index 5bf722d1ec6f30c0107224c6dc21514b0e6047ae..8bfbe37c543247a4d6d4dab8dee24f3e77a97871 100644
--- a/app/finders/projects_finder.rb
+++ b/app/finders/projects_finder.rb
@@ -28,34 +28,56 @@ class ProjectsFinder < UnionFinder
   end
 
   def execute
-    items = init_collection
-    items = items.map do |item|
-      item = by_ids(item)
-      item = by_personal(item)
-      item = by_starred(item)
-      item = by_trending(item)
-      item = by_visibilty_level(item)
-      item = by_tags(item)
-      item = by_search(item)
-      by_archived(item)
-    end
-    items = union(items)
-    sort(items)
+    collection = init_collection
+    collection = by_ids(collection)
+    collection = by_personal(collection)
+    collection = by_starred(collection)
+    collection = by_trending(collection)
+    collection = by_visibilty_level(collection)
+    collection = by_tags(collection)
+    collection = by_search(collection)
+    collection = by_archived(collection)
+
+    sort(collection)
   end
 
   private
 
   def init_collection
-    projects = []
+    if current_user
+      collection_with_user
+    else
+      collection_without_user
+    end
+  end
 
-    if params[:owned].present?
-      projects << current_user.owned_projects if current_user
+  def collection_with_user
+    if owned_projects?
+      current_user.owned_projects
     else
-      projects << current_user.authorized_projects if current_user
-      projects << Project.unscoped.public_to_user(current_user) unless params[:non_public].present?
+      if private_only?
+        current_user.authorized_projects
+      else
+        Project.public_or_visible_to_user(current_user)
+      end
     end
+  end
+
+  # Builds a collection for an anonymous user.
+  def collection_without_user
+    if private_only? || owned_projects?
+      Project.none
+    else
+      Project.public_to_user
+    end
+  end
+
+  def owned_projects?
+    params[:owned].present?
+  end
 
-    projects
+  def private_only?
+    params[:non_public].present?
   end
 
   def by_ids(items)
diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb
index c358f23f54176f382322509774543d071d7725cb..3fe37c753813540824ab48194d86253b4d1460ba 100644
--- a/app/finders/todos_finder.rb
+++ b/app/finders/todos_finder.rb
@@ -83,6 +83,8 @@ class TodosFinder
     if project?
       @project = Project.find(params[:project_id])
 
+      @project = nil if @project.pending_delete?
+
       unless Ability.allowed?(current_user, :read_project, @project)
         @project = nil
       end
diff --git a/app/finders/users_finder.rb b/app/finders/users_finder.rb
index dbd50d1db7ca9788e8aaef66f51930efbddc72ed..07deceb827bb7999ac338b573729c8616eef73ed 100644
--- a/app/finders/users_finder.rb
+++ b/app/finders/users_finder.rb
@@ -60,13 +60,13 @@ class UsersFinder
   end
 
   def by_external_identity(users)
-    return users unless current_user.admin? && params[:extern_uid] && params[:provider]
+    return users unless current_user&.admin? && params[:extern_uid] && params[:provider]
 
     users.joins(:identities).merge(Identity.with_extern_uid(params[:provider], params[:extern_uid]))
   end
 
   def by_external(users)
-    return users = users.where.not(external: true) unless current_user.admin?
+    return users = users.where.not(external: true) unless current_user&.admin?
     return users unless params[:external]
 
     users.external
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 2bfc7586adc93e5dca7e66b68da29f8993721b1d..1c165700b19449105a4041e742dcfa08f9982a01 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -68,7 +68,7 @@ module ApplicationHelper
     end
   end
 
-  def avatar_icon(user_or_email = nil, size = nil, scale = 2)
+  def avatar_icon(user_or_email = nil, size = nil, scale = 2, only_path: true)
     user =
       if user_or_email.is_a?(User)
         user_or_email
@@ -77,7 +77,7 @@ module ApplicationHelper
       end
 
     if user
-      user.avatar_url(size: size) || default_avatar
+      user.avatar_url(size: size, only_path: only_path) || default_avatar
     else
       gravatar_icon(user_or_email, size, scale)
     end
@@ -131,10 +131,7 @@ module ApplicationHelper
   end
 
   def body_data_page
-    path = controller.controller_path.split('/')
-    namespace = path.first if path.second
-
-    [namespace, controller.controller_name, controller.action_name].compact.join(':')
+    [*controller.controller_path.split('/'), controller.action_name].compact.join(':')
   end
 
   # shortcut for gitlab config
@@ -167,9 +164,9 @@ module ApplicationHelper
     css_classes = short_format ? 'js-short-timeago' : 'js-timeago'
     css_classes << " #{html_class}" unless html_class.blank?
 
-    element = content_tag :time, time.strftime("%b %d, %Y"),
+    element = content_tag :time, l(time, format: "%b %d, %Y"),
       class: css_classes,
-      title: time.to_time.in_time_zone.to_s(:medium),
+      title: l(time.to_time.in_time_zone, format: :timeago_tooltip),
       datetime: time.to_time.getutc.iso8601,
       data: {
         toggle: 'tooltip',
@@ -300,4 +297,8 @@ module ApplicationHelper
       "https://www.twitter.com/#{name}"
     end
   end
+
+  def show_new_nav?
+    cookies["new_nav"] == "true"
+  end
 end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index ca326dd0627f022d55a98a41597041ad4b9901ea..f652f4901b7365efcd238ffe2bbf5288ec79cc3f 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -34,17 +34,17 @@ module ApplicationSettingsHelper
 
   # Return a group of checkboxes that use Bootstrap's button plugin for a
   # toggle button effect.
-  def restricted_level_checkboxes(help_block_id)
+  def restricted_level_checkboxes(help_block_id, checkbox_name)
     Gitlab::VisibilityLevel.options.map do |name, level|
       checked = restricted_visibility_levels(true).include?(level)
       css_class = checked ? 'active' : ''
-      checkbox_name = "application_setting[restricted_visibility_levels][]"
+      tag_name = "application_setting_visibility_level_#{level}"
 
-      label_tag(name, class: css_class) do
+      label_tag(tag_name, class: css_class) do
         check_box_tag(checkbox_name, level, checked,
                       autocomplete: 'off',
                       'aria-describedby' => help_block_id,
-                      id: name) + visibility_level_icon(level) + name
+                      id: tag_name) + visibility_level_icon(level) + name
       end
     end
   end
diff --git a/app/helpers/award_emoji_helper.rb b/app/helpers/award_emoji_helper.rb
index 024cf38469ec1e41c9b7d3971bab2c8df4804ec0..86b19368cfd598d2cdd0aff7a93fc237da169e6e 100644
--- a/app/helpers/award_emoji_helper.rb
+++ b/app/helpers/award_emoji_helper.rb
@@ -7,7 +7,7 @@ module AwardEmojiHelper
       if awardable.for_personal_snippet?
         toggle_award_emoji_snippet_note_path(awardable.noteable, awardable)
       else
-        toggle_award_emoji_namespace_project_note_path(@project.namespace, @project, awardable.id)
+        toggle_award_emoji_project_note_path(@project, awardable.id)
       end
     else
       url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable])
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 3efa7c36057c1de1841fea3461b67576a94b81bf..e964d7a5e1690ba8d822855486f769174b645ad6 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -9,7 +9,7 @@ module BlobHelper
   end
 
   def edit_path(project = @project, ref = @ref, path = @path, options = {})
-    namespace_project_edit_blob_path(project.namespace, project,
+    project_edit_blob_path(project,
                                      tree_join(ref, path),
                                      options[:link_opts])
   end
@@ -33,7 +33,7 @@ module BlobHelper
         notice: edit_in_new_fork_notice,
         notice_now: edit_in_new_fork_notice_now
       }
-      fork_path = namespace_project_forks_path(project.namespace, project, namespace_key: current_user.namespace.id, continue: continue_params)
+      fork_path = project_forks_path(project, namespace_key: current_user.namespace.id, continue: continue_params)
 
       button_tag 'Edit',
         class: "#{common_classes} js-edit-blob-link-fork-toggler",
@@ -62,7 +62,7 @@ module BlobHelper
         notice: edit_in_new_fork_notice + " Try to #{action} this file again.",
         notice_now: edit_in_new_fork_notice_now
       }
-      fork_path = namespace_project_forks_path(project.namespace, project, namespace_key: current_user.namespace.id, continue: continue_params)
+      fork_path = project_forks_path(project, namespace_key: current_user.namespace.id, continue: continue_params)
 
       button_tag label,
         class: "#{common_classes} js-edit-blob-link-fork-toggler",
@@ -120,15 +120,15 @@ module BlobHelper
 
   def blob_raw_url
     if @build && @entry
-      raw_namespace_project_job_artifacts_path(@project.namespace, @project, @build, path: @entry.path)
+      raw_project_job_artifacts_path(@project, @build, path: @entry.path)
     elsif @snippet
       if @snippet.project_id
-        raw_namespace_project_snippet_path(@project.namespace, @project, @snippet)
+        raw_project_snippet_path(@project, @snippet)
       else
         raw_snippet_path(@snippet)
       end
     elsif @blob
-      namespace_project_raw_path(@project.namespace, @project, @id)
+      project_raw_path(@project, @id)
     end
   end
 
@@ -279,12 +279,12 @@ module BlobHelper
     options = []
 
     if can?(current_user, :create_issue, project)
-      options << link_to("submit an issue", new_namespace_project_issue_path(project.namespace, project))
+      options << link_to("submit an issue", new_project_issue_path(project))
     end
 
     merge_project = can?(current_user, :create_merge_request, project) ? project : (current_user && current_user.fork_of(project))
     if merge_project
-      options << link_to("create a merge request", new_namespace_project_merge_request_path(project.namespace, project))
+      options << link_to("create a merge request", project_new_merge_request_path(project))
     end
 
     options
diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb
index e2df52e3833231c6a35f7775000becf87074adcd..8b33c362a9c17bcb0ef2f62ed2ffa0e53c0394cf 100644
--- a/app/helpers/boards_helper.rb
+++ b/app/helpers/boards_helper.rb
@@ -3,12 +3,12 @@ module BoardsHelper
     board = @board || @boards.first
 
     {
-      endpoint: namespace_project_boards_path(@project.namespace, @project),
+      endpoint: project_boards_path(@project),
       board_id: board.id,
       disabled: "#{!can?(current_user, :admin_list, @project)}",
-      issue_link_base: namespace_project_issues_path(@project.namespace, @project),
+      issue_link_base: project_issues_path(@project),
       root_path: root_path,
-      bulk_update_path: bulk_update_namespace_project_issues_path(@project.namespace, @project),
+      bulk_update_path: bulk_update_project_issues_path(@project),
       default_avatar: image_path(default_avatar)
     }
   end
diff --git a/app/helpers/branches_helper.rb b/app/helpers/branches_helper.rb
index 59519c1335bfb703376c2ccf5f8376c8614591bd..686437fc99a551b1cd6282c4546e40a4cf9feb44 100644
--- a/app/helpers/branches_helper.rb
+++ b/app/helpers/branches_helper.rb
@@ -7,7 +7,7 @@ module BranchesHelper
 
     options = exist_opts.merge(options)
 
-    namespace_project_branches_path(@project.namespace, @project, @id, options)
+    project_branches_path(@project, @id, options)
   end
 
   def can_push_branch?(project, branch_name)
diff --git a/app/helpers/builds_helper.rb b/app/helpers/builds_helper.rb
index f0a0d245dc080c984eff8e6d8ae78132e7969022..85bc784d53cc9c646114a10a1dc5b0159a2974dc 100644
--- a/app/helpers/builds_helper.rb
+++ b/app/helpers/builds_helper.rb
@@ -20,8 +20,8 @@ module BuildsHelper
 
   def javascript_build_options
     {
-      page_url: namespace_project_job_url(@project.namespace, @project, @build),
-      build_url: namespace_project_job_url(@project.namespace, @project, @build, :json),
+      page_url: project_job_url(@project, @build),
+      build_url: project_job_url(@project, @build, :json),
       build_status: @build.status,
       build_stage: @build.stage,
       log_state: ''
@@ -31,7 +31,7 @@ module BuildsHelper
   def build_failed_issue_options
     {
       title: "Build Failed ##{@build.id}",
-      description: namespace_project_job_url(@project.namespace, @project, @build)
+      description: project_job_url(@project, @build)
     }
   end
 end
diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb
index 00464810054250d31a9b76b8e97061b30158b859..ba84dbe4a7a87c3a9c95ca30c97841bca6e8b8b8 100644
--- a/app/helpers/button_helper.rb
+++ b/app/helpers/button_helper.rb
@@ -50,10 +50,17 @@ module ButtonHelper
 
   def http_clone_button(project, placement = 'right', append_link: true)
     klass = 'http-selector'
-    klass << ' has-tooltip' if current_user.try(:require_password?)
+    klass << ' has-tooltip' if current_user.try(:require_password?) || current_user.try(:require_personal_access_token?)
 
     protocol = gitlab_config.protocol.upcase
 
+    tooltip_title =
+      if current_user.try(:require_password?)
+        _("Set a password on your account to pull or push via %{protocol}.") % { protocol: protocol }
+      else
+        _("Create a personal access token on your account to pull or push via %{protocol}.") % { protocol: protocol }
+      end
+
     content_tag (append_link ? :a : :span), protocol,
       class: klass,
       href: (project.http_url_to_repo if append_link),
@@ -61,7 +68,7 @@ module ButtonHelper
         html: true,
         placement: placement,
         container: 'body',
-        title: _("Set a password on your account to pull or push via %{protocol}") % { protocol: protocol }
+        title: tooltip_title
       }
   end
 
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index 21c0eb8b54ca743281746e0248c3c86381475e04..8022547a6ad903e55393341dd1c1bfd8cea597ed 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -8,7 +8,7 @@
 module CiStatusHelper
   def ci_status_path(pipeline)
     project = pipeline.project
-    namespace_project_pipeline_path(project.namespace, project, pipeline)
+    project_pipeline_path(project, pipeline)
   end
 
   def ci_label_for_status(status)
@@ -99,10 +99,7 @@ module CiStatusHelper
 
   def render_project_pipeline_status(pipeline_status, tooltip_placement: 'auto left')
     project = pipeline_status.project
-    path = pipelines_namespace_project_commit_path(
-      project.namespace,
-      project,
-      pipeline_status.sha)
+    path = pipelines_project_commit_path(project, pipeline_status.sha)
 
     render_status_with_link(
       'commit',
@@ -113,10 +110,7 @@ module CiStatusHelper
 
   def render_commit_status(commit, ref: nil, tooltip_placement: 'auto left')
     project = commit.project
-    path = pipelines_namespace_project_commit_path(
-      project.namespace,
-      project,
-      commit)
+    path = pipelines_project_commit_path(project, commit)
 
     render_status_with_link(
       'commit',
@@ -127,7 +121,7 @@ module CiStatusHelper
 
   def render_pipeline_status(pipeline, tooltip_placement: 'auto left')
     project = pipeline.project
-    path = namespace_project_pipeline_path(project.namespace, project, pipeline)
+    path = project_pipeline_path(project, pipeline)
     render_status_with_link('pipeline', pipeline.status, path, tooltip_placement: tooltip_placement)
   end
 
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index 5b5cdebe9197119d8dbf0e77cedfb0abac10ad67..d08e346d605d16ec0b1c0fc02f940728ba138310 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -30,7 +30,7 @@ module CommitsHelper
     crumbs = content_tag(:li) do
       link_to(
         @project.path,
-        namespace_project_commits_path(@project.namespace, @project, @ref)
+        project_commits_path(@project, @ref)
       )
     end
 
@@ -42,8 +42,7 @@ module CommitsHelper
           # The text is just the individual part, but the link needs all the parts before it
           link_to(
             part,
-            namespace_project_commits_path(
-              @project.namespace,
+            project_commits_path(
               @project,
               tree_join(@ref, parts[0..i].join('/'))
             )
@@ -85,21 +84,21 @@ module CommitsHelper
 
     if @path.blank?
       return link_to(
-        "Browse Files",
-        namespace_project_tree_path(project.namespace, project, commit),
+        _("Browse Files"),
+        project_tree_path(project, commit),
         class: "btn btn-default"
       )
     elsif @repo.blob_at(commit.id, @path)
       return link_to(
-        "Browse File",
-        namespace_project_blob_path(project.namespace, project,
+        _("Browse File"),
+        project_blob_path(project,
                                     tree_join(commit.id, @path)),
         class: "btn btn-default"
       )
     elsif @path.present?
       return link_to(
-        "Browse Directory",
-        namespace_project_tree_path(project.namespace, project,
+        _("Browse Directory"),
+        project_tree_path(project,
                                     tree_join(commit.id, @path)),
         class: "btn btn-default"
       )
@@ -165,7 +164,7 @@ module CommitsHelper
         notice: "#{edit_in_new_fork_notice} Try to #{action} this commit again.",
         notice_now: edit_in_new_fork_notice_now
       }
-      fork_path = namespace_project_forks_path(@project.namespace, @project,
+      fork_path = project_forks_path(@project,
         namespace_key: current_user.namespace.id,
         continue: continue_params)
 
@@ -175,7 +174,7 @@ module CommitsHelper
 
   def view_file_button(commit_sha, diff_new_path, project)
     link_to(
-      namespace_project_blob_path(project.namespace, project,
+      project_blob_path(project,
                                   tree_join(commit_sha, diff_new_path)),
       class: 'btn view-file js-view-file'
     ) do
diff --git a/app/helpers/compare_helper.rb b/app/helpers/compare_helper.rb
index 2aa0449c46e761d817030a3192d0cd3782b6037f..2c28dd81c87b622b0e02378b7941047a301df117 100644
--- a/app/helpers/compare_helper.rb
+++ b/app/helpers/compare_helper.rb
@@ -9,8 +9,7 @@ module CompareHelper
   end
 
   def create_mr_path(from = params[:from], to = params[:to], project = @project)
-    new_namespace_project_merge_request_path(
-      project.namespace,
+    project_new_merge_request_path(
       project,
       merge_request: {
         source_branch: to,
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index 06822747d11fcbfe78042bca7a1286fe18b72003..926502bf23922cab2c587be6ad63ce66e922e7a2 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -66,12 +66,12 @@ module DiffHelper
 
     discussions_left = discussions_right = nil
 
-    if left && (left.unchanged? || left.discussable?)
+    if left && left.discussable? && (left.unchanged? || left.removed?)
       line_code = diff_file.line_code(left)
       discussions_left = @grouped_diff_discussions[line_code]
     end
 
-    if right&.discussable?
+    if right && right.discussable? && right.added?
       line_code = diff_file.line_code(right)
       discussions_right = @grouped_diff_discussions[line_code]
     end
@@ -103,18 +103,18 @@ module DiffHelper
   end
 
   def diff_file_blob_raw_path(diff_file)
-    namespace_project_raw_path(@project.namespace, @project, tree_join(diff_file.content_sha, diff_file.file_path))
+    project_raw_path(@project, tree_join(diff_file.content_sha, diff_file.file_path))
   end
 
   def diff_file_old_blob_raw_path(diff_file)
     sha = diff_file.old_content_sha
     return unless sha
-    namespace_project_raw_path(@project.namespace, @project, tree_join(diff_file.old_content_sha, diff_file.old_path))
+    project_raw_path(@project, tree_join(diff_file.old_content_sha, diff_file.old_path))
   end
 
   def diff_file_html_data(project, diff_file_path, diff_commit_id)
     {
-      blob_diff_path: namespace_project_blob_diff_path(project.namespace, project,
+      blob_diff_path: project_blob_diff_path(project,
                                                        tree_join(diff_commit_id, diff_file_path)),
       view: diff_view
     }
@@ -142,7 +142,7 @@ module DiffHelper
     diff_file = viewer.diff_file
     options = []
 
-    blob_url = namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.content_sha, diff_file.file_path))
+    blob_url = project_blob_path(@project, tree_join(diff_file.content_sha, diff_file.file_path))
     options << link_to('view the blob', blob_url)
 
     options
@@ -163,17 +163,17 @@ module DiffHelper
   end
 
   def commit_diff_whitespace_link(project, commit, options)
-    url = namespace_project_commit_path(project.namespace, project, commit.id, params_with_whitespace)
+    url = project_commit_path(project, commit.id, params_with_whitespace)
     toggle_whitespace_link(url, options)
   end
 
   def diff_merge_request_whitespace_link(project, merge_request, options)
-    url = diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, params_with_whitespace)
+    url = diffs_project_merge_request_path(project, merge_request, params_with_whitespace)
     toggle_whitespace_link(url, options)
   end
 
   def diff_compare_whitespace_link(project, from, to, options)
-    url = namespace_project_compare_path(project.namespace, project, from, to, params_with_whitespace)
+    url = project_compare_path(project, from, to, params_with_whitespace)
     toggle_whitespace_link(url, options)
   end
 
diff --git a/app/helpers/environment_helper.rb b/app/helpers/environment_helper.rb
index ff8550439d0e4bd14882f678067e8e715eab8310..1e78a189c085612d24f37ad5cd63a96754726ba8 100644
--- a/app/helpers/environment_helper.rb
+++ b/app/helpers/environment_helper.rb
@@ -8,7 +8,7 @@ module EnvironmentHelper
   def environment_link_for_build(project, build)
     environment = environment_for_build(project, build)
     if environment
-      link_to environment.name, namespace_project_environment_path(project.namespace, project, environment)
+      link_to environment.name, project_environment_path(project, environment)
     else
       content_tag :span, build.expanded_environment_name
     end
diff --git a/app/helpers/environments_helper.rb b/app/helpers/environments_helper.rb
index 515e802e01e4831847c4e539a02e8be52cd9a9fd..4ce89f89fa990fb9c87cb373e815b8a8fe258524 100644
--- a/app/helpers/environments_helper.rb
+++ b/app/helpers/environments_helper.rb
@@ -1,7 +1,7 @@
 module EnvironmentsHelper
   def environments_list_data
     {
-      endpoint: namespace_project_environments_path(@project.namespace, @project, format: :json)
+      endpoint: project_environments_path(@project, format: :json)
     }
   end
 end
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index 751d61955b764d6b5b78cf347593f713dca167d1..48c87dca2178e921524ab38e9157ef353d85a45c 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -99,13 +99,12 @@ module EventsHelper
 
   def event_feed_url(event)
     if event.issue?
-      namespace_project_issue_url(event.project.namespace, event.project,
+      project_issue_url(event.project,
                                   event.issue)
     elsif event.merge_request?
-      namespace_project_merge_request_url(event.project.namespace,
-                                          event.project, event.merge_request)
+      project_merge_request_url(event.project, event.merge_request)
     elsif event.commit_note?
-      namespace_project_commit_url(event.project.namespace, event.project,
+      project_commit_url(event.project,
                                    event.note_target)
     elsif event.note?
       if event.note_target
@@ -119,15 +118,15 @@ module EventsHelper
   def push_event_feed_url(event)
     if event.push_with_commits? && event.md_ref?
       if event.commits_count > 1
-        namespace_project_compare_url(event.project.namespace, event.project,
+        project_compare_url(event.project,
                                       from: event.commit_from, to:
                                       event.commit_to)
       else
-        namespace_project_commit_url(event.project.namespace, event.project,
+        project_commit_url(event.project,
                                      id: event.commit_to)
       end
     else
-      namespace_project_commits_url(event.project.namespace, event.project,
+      project_commits_url(event.project,
                                     event.ref_name)
     end
   end
@@ -146,15 +145,9 @@ module EventsHelper
 
   def event_note_target_path(event)
     if event.commit_note?
-      namespace_project_commit_path(event.project.namespace,
-                                    event.project,
-                                    event.note_target,
-                                    anchor: dom_id(event.target))
+      project_commit_path(event.project, event.note_target, anchor: dom_id(event.target))
     elsif event.project_snippet_note?
-      namespace_project_snippet_path(event.project.namespace,
-                                     event.project,
-                                     event.note_target,
-                                     anchor: dom_id(event.target))
+      project_snippet_path(event.project, event.note_target, anchor: dom_id(event.target))
     else
       polymorphic_path([event.project.namespace.becomes(Namespace),
                         event.project, event.note_target],
diff --git a/app/helpers/external_wiki_helper.rb b/app/helpers/external_wiki_helper.rb
index defd87d6bbe620991e9f7158058780f3149acf72..8cf890b74a87dd7dcd5c74c11dd2bac3eba1c2ce 100644
--- a/app/helpers/external_wiki_helper.rb
+++ b/app/helpers/external_wiki_helper.rb
@@ -4,7 +4,7 @@ module ExternalWikiHelper
     if external_wiki_service
       external_wiki_service.properties['external_wiki_url']
     else
-      namespace_project_wiki_path(project.namespace, project, :home)
+      project_wiki_path(project, :home)
     end
   end
 end
diff --git a/app/helpers/form_helper.rb b/app/helpers/form_helper.rb
index 014fc46b1304bc34643fc30af7a1c8f18e2747c7..9247b1f72de535fac202e7224cca115d6e1083fe 100644
--- a/app/helpers/form_helper.rb
+++ b/app/helpers/form_helper.rb
@@ -8,16 +8,16 @@ module FormHelper
     content_tag(:div, class: 'alert alert-danger', id: 'error_explanation') do
       content_tag(:h4, headline) <<
         content_tag(:ul) do
-          model.errors.full_messages.
-            map { |msg| content_tag(:li, msg) }.
-            join.
-            html_safe
+          model.errors.full_messages
+            .map { |msg| content_tag(:li, msg) }
+            .join
+            .html_safe
         end
     end
   end
 
-  def issue_dropdown_options(issuable, has_multiple_assignees = true)
-    options = {
+  def issue_assignees_dropdown_options
+    {
       toggle_class: 'js-user-search js-assignee-search js-multiselect js-save-user-data',
       title: 'Select assignee',
       filter: true,
@@ -27,8 +27,8 @@ module FormHelper
         first_user: current_user&.username,
         null_user: true,
         current_user: true,
-        project_id: issuable.project.try(:id),
-        field_name: "#{issuable.class.model_name.param_key}[assignee_ids][]",
+        project_id: @project.id,
+        field_name: 'issue[assignee_ids][]',
         default_label: 'Unassigned',
         'max-select': 1,
         'dropdown-header': 'Assignee',
@@ -38,13 +38,5 @@ module FormHelper
         current_user_info: current_user.to_json(only: [:id, :name])
       }
     }
-
-    if has_multiple_assignees
-      options[:title] = 'Select assignee(s)'
-      options[:data][:'dropdown-header'] = 'Assignee(s)'
-      options[:data].delete(:'max-select')
-    end
-
-    options
   end
 end
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index 8c7af62e19968cd66668854cc9e61265ad9dd00a..b5f4bbe97dc9c47a39d03072a0923d9ca4e97516 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -1,144 +1,89 @@
-# Shorter routing method for project and project items
-# Since update to rails 4.1.9 we are now allowed to use `/` in project routing
-# so we use nested routing for project resources which include project and
-# project namespace. To avoid writing long methods every time we define shortcuts for
-# some of routing.
-#
-# For example instead of this:
-#
-#   namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request)
-#
-# We can simply use shortcut:
-#
-#   merge_request_path(merge_request)
-#
+# Shorter routing method for some project items
 module GitlabRoutingHelper
-  # Project
-  def project_path(project, *args)
-    namespace_project_path(project.namespace, project, *args)
-  end
-
-  def project_url(project, *args)
-    namespace_project_url(project.namespace, project, *args)
-  end
-
-  def edit_project_path(project, *args)
-    edit_namespace_project_path(project.namespace, project, *args)
-  end
-
-  def edit_project_url(project, *args)
-    edit_namespace_project_url(project.namespace, project, *args)
-  end
-
-  def project_files_path(project, *args)
-    namespace_project_tree_path(project.namespace, project, @ref || project.repository.root_ref)
-  end
-
-  def project_commits_path(project, *args)
-    namespace_project_commits_path(project.namespace, project, @ref || project.repository.root_ref)
-  end
-
-  def project_pipelines_path(project, *args)
-    namespace_project_pipelines_path(project.namespace, project, *args)
-  end
-
-  def project_environments_path(project, *args)
-    namespace_project_environments_path(project.namespace, project, *args)
-  end
+  extend ActiveSupport::Concern
 
-  def project_cycle_analytics_path(project, *args)
-    namespace_project_cycle_analytics_path(project.namespace, project, *args)
+  # Project
+  def project_tree_path(project, ref = nil, *args)
+    namespace_project_tree_path(project.namespace, project, ref || @ref || project.repository.root_ref, *args) # rubocop:disable Cop/ProjectPathHelper
   end
 
-  def project_jobs_path(project, *args)
-    namespace_project_jobs_path(project.namespace, project, *args)
+  def project_commits_path(project, ref = nil, *args)
+    namespace_project_commits_path(project.namespace, project, ref || @ref || project.repository.root_ref, *args) # rubocop:disable Cop/ProjectPathHelper
   end
 
   def project_ref_path(project, ref_name, *args)
-    namespace_project_commits_path(project.namespace, project, ref_name, *args)
-  end
-
-  def project_container_registry_path(project, *args)
-    namespace_project_container_registry_index_path(project.namespace, project, *args)
-  end
-
-  def activity_project_path(project, *args)
-    activity_namespace_project_path(project.namespace, project, *args)
+    project_commits_path(project, ref_name, *args)
   end
 
   def runners_path(project, *args)
-    namespace_project_runners_path(project.namespace, project, *args)
+    project_runners_path(project, *args)
   end
 
   def runner_path(runner, *args)
-    namespace_project_runner_path(@project.namespace, @project, runner, *args)
+    project_runner_path(@project, runner, *args)
   end
 
   def environment_path(environment, *args)
-    namespace_project_environment_path(environment.project.namespace, environment.project, environment, *args)
+    project_environment_path(environment.project, environment, *args)
   end
 
   def environment_metrics_path(environment, *args)
-    metrics_namespace_project_environment_path(environment.project.namespace, environment.project, environment, *args)
+    metrics_project_environment_path(environment.project, environment, *args)
   end
 
   def issue_path(entity, *args)
-    namespace_project_issue_path(entity.project.namespace, entity.project, entity, *args)
+    project_issue_path(entity.project, entity, *args)
   end
 
   def merge_request_path(entity, *args)
-    namespace_project_merge_request_path(entity.project.namespace, entity.project, entity, *args)
+    project_merge_request_path(entity.project, entity, *args)
   end
 
   def pipeline_path(pipeline, *args)
-    namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, *args)
+    project_pipeline_path(pipeline.project, pipeline.id, *args)
   end
 
   def milestone_path(entity, *args)
-    namespace_project_milestone_path(entity.project.namespace, entity.project, entity, *args)
+    project_milestone_path(entity.project, entity, *args)
   end
 
   def issue_url(entity, *args)
-    namespace_project_issue_url(entity.project.namespace, entity.project, entity, *args)
+    project_issue_url(entity.project, entity, *args)
   end
 
   def merge_request_url(entity, *args)
-    namespace_project_merge_request_url(entity.project.namespace, entity.project, entity, *args)
+    project_merge_request_url(entity.project, entity, *args)
   end
 
   def pipeline_url(pipeline, *args)
-    namespace_project_pipeline_url(pipeline.project.namespace, pipeline.project, pipeline.id, *args)
+    project_pipeline_url(pipeline.project, pipeline.id, *args)
   end
 
   def pipeline_job_url(pipeline, build, *args)
-    namespace_project_job_url(pipeline.project.namespace, pipeline.project, build.id, *args)
+    project_job_url(pipeline.project, build.id, *args)
   end
 
   def commits_url(entity, *args)
-    namespace_project_commits_url(entity.project.namespace, entity.project, entity.ref, *args)
+    project_commits_url(entity.project, entity.ref, *args)
   end
 
   def commit_url(entity, *args)
-    namespace_project_commit_url(entity.project.namespace, entity.project, entity.sha, *args)
-  end
-
-  def project_snippet_url(entity, *args)
-    namespace_project_snippet_url(entity.project.namespace, entity.project, entity, *args)
+    project_commit_url(entity.project, entity.sha, *args)
   end
 
   def preview_markdown_path(project, *args)
     if @snippet.is_a?(PersonalSnippet)
       preview_markdown_snippets_path
     else
-      preview_markdown_namespace_project_path(project.namespace, project, *args)
+      preview_markdown_project_path(project, *args)
     end
   end
 
   def toggle_subscription_path(entity, *args)
     if entity.is_a?(Issue)
-      toggle_subscription_namespace_project_issue_path(entity.project.namespace, entity.project, entity)
+      toggle_subscription_project_issue_path(entity.project, entity)
     else
-      toggle_subscription_namespace_project_merge_request_path(entity.project.namespace, entity.project, entity)
+      toggle_subscription_project_merge_request_path(entity.project, entity)
     end
   end
 
@@ -152,32 +97,27 @@ module GitlabRoutingHelper
 
   ## Members
   def project_members_url(project, *args)
-    namespace_project_project_members_url(project.namespace, project)
+    project_project_members_url(project)
   end
 
   def project_member_path(project_member, *args)
-    namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member)
+    project_project_member_path(project_member.source, project_member)
   end
 
   def request_access_project_members_path(project, *args)
-    request_access_namespace_project_project_members_path(project.namespace, project)
+    request_access_project_project_members_path(project)
   end
 
   def leave_project_members_path(project, *args)
-    leave_namespace_project_project_members_path(project.namespace, project)
+    leave_project_project_members_path(project)
   end
 
   def approve_access_request_project_member_path(project_member, *args)
-    approve_access_request_namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member)
+    approve_access_request_project_project_member_path(project_member.source, project_member)
   end
 
   def resend_invite_project_member_path(project_member, *args)
-    resend_invite_namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member)
-  end
-
-  # Snippets
-  def personal_snippet_url(snippet, *args)
-    snippet_url(snippet)
+    resend_invite_project_project_member_path(project_member.source, project_member)
   end
 
   # Groups
@@ -211,50 +151,37 @@ module GitlabRoutingHelper
 
   def artifacts_action_path(path, project, build)
     action, path_params = path.split('/', 2)
-    args = [project.namespace, project, build, path_params]
+    args = [project, build, path_params]
 
     case action
     when 'download'
-      download_namespace_project_job_artifacts_path(*args)
+      download_project_job_artifacts_path(*args)
     when 'browse'
-      browse_namespace_project_job_artifacts_path(*args)
+      browse_project_job_artifacts_path(*args)
     when 'file'
-      file_namespace_project_job_artifacts_path(*args)
+      file_project_job_artifacts_path(*args)
     when 'raw'
-      raw_namespace_project_job_artifacts_path(*args)
+      raw_project_job_artifacts_path(*args)
     end
   end
 
   # Pipeline Schedules
   def pipeline_schedules_path(project, *args)
-    namespace_project_pipeline_schedules_path(project.namespace, project, *args)
+    project_pipeline_schedules_path(project, *args)
   end
 
   def pipeline_schedule_path(schedule, *args)
     project = schedule.project
-    namespace_project_pipeline_schedule_path(project.namespace, project, schedule, *args)
+    project_pipeline_schedule_path(project, schedule, *args)
   end
 
   def edit_pipeline_schedule_path(schedule)
     project = schedule.project
-    edit_namespace_project_pipeline_schedule_path(project.namespace, project, schedule)
+    edit_project_pipeline_schedule_path(project, schedule)
   end
 
   def take_ownership_pipeline_schedule_path(schedule, *args)
     project = schedule.project
-    take_ownership_namespace_project_pipeline_schedule_path(project.namespace, project, schedule, *args)
-  end
-
-  # Settings
-  def project_settings_integrations_path(project, *args)
-    namespace_project_settings_integrations_path(project.namespace, project, *args)
-  end
-
-  def project_settings_members_path(project, *args)
-    namespace_project_settings_members_path(project.namespace, project, *args)
-  end
-
-  def project_settings_ci_cd_path(project, *args)
-    namespace_project_settings_ci_cd_path(project.namespace, project, *args)
+    take_ownership_project_pipeline_schedule_path(project, schedule, *args)
   end
 end
diff --git a/app/helpers/graph_helper.rb b/app/helpers/graph_helper.rb
index c2ab80f2e0d4f4f0b49115a9627dd48fa5d737ba..2e9b72e9613867dbd41a44aafd5af1ead3b4e85b 100644
--- a/app/helpers/graph_helper.rb
+++ b/app/helpers/graph_helper.rb
@@ -17,13 +17,10 @@ module GraphHelper
     ids.zip(parent_spaces)
   end
 
-  def success_ratio(success_builds, failed_builds)
-    failed_builds = failed_builds.count(:all)
-    success_builds = success_builds.count(:all)
+  def success_ratio(counts)
+    return 100 if counts[:failed].zero?
 
-    return 100 if failed_builds.zero?
-
-    ratio = (success_builds.to_f / (success_builds + failed_builds)) * 100
+    ratio = (counts[:success].to_f / (counts[:success] + counts[:failed])) * 100
     ratio.to_i
   end
 end
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index c003b01e226f1aaf00d79e51893fae6857b601a9..8cd61f738e1d56e74b38c7cc185aeaa6a6ebce5d 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -15,12 +15,13 @@ module GroupsHelper
     @has_group_title = true
     full_title = ''
 
-    group.ancestors.each do |parent|
-      full_title += link_to(simple_sanitize(parent.name), group_path(parent), class: 'group-path hidable')
+    group.ancestors.reverse.each do |parent|
+      full_title += group_title_link(parent, hidable: true)
+
       full_title += '<span class="hidable"> / </span>'.html_safe
     end
 
-    full_title += link_to(simple_sanitize(group.name), group_path(group), class: 'group-path')
+    full_title += group_title_link(group)
     full_title += ' &middot; '.html_safe + link_to(simple_sanitize(name), url, class: 'group-path') if name
 
     content_tag :span, class: 'group-title' do
@@ -56,4 +57,25 @@ module GroupsHelper
   def group_issues(group)
     IssuesFinder.new(current_user, group_id: group.id).execute
   end
+
+  def remove_group_message(group)
+    _("You are going to remove %{group_name}.\nRemoved groups CANNOT be restored!\nAre you ABSOLUTELY sure?") %
+      { group_name: group.name }
+  end
+
+  private
+
+  def group_title_link(group, hidable: false)
+    link_to(group_path(group), class: "group-path #{'hidable' if hidable}") do
+      output =
+        if show_new_nav?
+          image_tag(group_icon(group), class: "avatar-tile", width: 16, height: 16)
+        else
+          ""
+        end
+
+      output << simple_sanitize(group.name)
+      output.html_safe
+    end
+  end
 end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 5e8f0849969fa3597b26a4f86f7575d849681aee..b5366519ed99d63317a6ed692e0c2af1aad9df7f 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -26,9 +26,9 @@ module IssuablesHelper
     project = issuable.project
 
     if issuable.is_a?(MergeRequest)
-      namespace_project_merge_request_path(project.namespace, project, issuable.iid, :json)
+      project_merge_request_path(project, issuable.iid, :json)
     else
-      namespace_project_issue_path(project.namespace, project, issuable.iid, :json)
+      project_issue_path(project, issuable.iid, :json)
     end
   end
 
@@ -138,8 +138,8 @@ module IssuablesHelper
     end
 
     output << "&ensp;".html_safe
-    output << content_tag(:span, issuable.task_status, id: "task_status", class: "hidden-xs hidden-sm")
-    output << content_tag(:span, issuable.task_status_short, id: "task_status_short", class: "hidden-md hidden-lg")
+    output << content_tag(:span, (issuable.task_status if issuable.tasks?), id: "task_status", class: "hidden-xs hidden-sm")
+    output << content_tag(:span, (issuable.task_status_short if issuable.tasks?), id: "task_status_short", class: "hidden-md hidden-lg")
 
     output
   end
@@ -165,11 +165,7 @@ module IssuablesHelper
     }
 
     state_title = titles[state] || state.to_s.humanize
-
-    count =
-      Rails.cache.fetch(issuables_state_counter_cache_key(issuable_type, state), expires_in: 2.minutes) do
-        issuables_count_for_state(issuable_type, state)
-      end
+    count = issuables_count_for_state(issuable_type, state)
 
     html = content_tag(:span, state_title)
     html << " " << content_tag(:span, number_with_delimiter(count), class: 'badge')
@@ -201,7 +197,7 @@ module IssuablesHelper
 
   def issuable_initial_data(issuable)
     data = {
-      endpoint: namespace_project_issue_path(@project.namespace, @project, issuable),
+      endpoint: project_issue_path(@project, issuable),
       canUpdate: can?(current_user, :update_issue, issuable),
       canDestroy: can?(current_user, :destroy_issue, issuable),
       canMove: current_user ? issuable.can_move?(current_user) : false,
@@ -216,7 +212,8 @@ module IssuablesHelper
       initialTitleHtml: markdown_field(issuable, :title),
       initialTitleText: issuable.title,
       initialDescriptionHtml: markdown_field(issuable, :description),
-      initialDescriptionText: issuable.description
+      initialDescriptionText: issuable.description,
+      initialTaskStatus: issuable.task_status
     }
 
     data.merge!(updated_at_by(issuable))
@@ -236,6 +233,18 @@ module IssuablesHelper
     }
   end
 
+  def issuables_count_for_state(issuable_type, state, finder: nil)
+    finder ||= public_send("#{issuable_type}_finder")
+    cache_key = finder.state_counter_cache_key(state)
+
+    @counts ||= {}
+    @counts[cache_key] ||= Rails.cache.fetch(cache_key, expires_in: 2.minutes) do
+      finder.count_by_state
+    end
+
+    @counts[cache_key][state]
+  end
+
   private
 
   def sidebar_gutter_collapsed?
@@ -254,24 +263,6 @@ module IssuablesHelper
     end
   end
 
-  def issuables_count_for_state(issuable_type, state)
-    @counts ||= {}
-    @counts[issuable_type] ||= public_send("#{issuable_type}_finder").count_by_state
-    @counts[issuable_type][state]
-  end
-
-  IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page].freeze
-  private_constant :IRRELEVANT_PARAMS_FOR_CACHE_KEY
-
-  def issuables_state_counter_cache_key(issuable_type, state)
-    opts = params.with_indifferent_access
-    opts[:state] = state
-    opts.except!(*IRRELEVANT_PARAMS_FOR_CACHE_KEY)
-    opts.delete_if { |_, value| value.blank? }
-
-    hexdigest(['issuables_count', issuable_type, opts.sort].flatten.join('-'))
-  end
-
   def issuable_templates(issuable)
     @issuable_templates ||=
       case issuable
@@ -304,7 +295,7 @@ module IssuablesHelper
       mark_icon: (is_collapsed ? icon('check-square', class: 'todo-undone') : nil),
       issuable_id: issuable.id,
       issuable_type: issuable.class.name.underscore,
-      url: namespace_project_todos_path(@project.namespace, @project),
+      url: project_todos_path(@project),
       delete_path: (dashboard_todo_path(todo) if todo),
       placement: (is_collapsed ? 'left' : nil),
       container: (is_collapsed ? 'body' : nil)
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 82288f1da353e597b0599806d821e0e70e20d3ce..42b6cfdf02fe9c59951298526138c22d25ff7c5f 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -150,7 +150,7 @@ module IssuesHelper
              Gitlab::UrlBuilder.build(single_discussion.first_note)
            else
              project = merge_request.project
-             namespace_project_merge_request_path(project.namespace, project, merge_request)
+             project_merge_request_path(project, merge_request)
            end
 
     link_to link_text, path
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index 4e6e68059204c1a9f05f7b7a27d91315ec2d32d0..4b99de1b6a5c61deb314964456d150e4a3767263 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -57,14 +57,14 @@ module LabelsHelper
   def edit_label_path(label)
     case label
     when GroupLabel then edit_group_label_path(label.group, label)
-    when ProjectLabel then edit_namespace_project_label_path(label.project.namespace, label.project, label)
+    when ProjectLabel then edit_project_label_path(label.project, label)
     end
   end
 
   def destroy_label_path(label)
     case label
     when GroupLabel then group_label_path(label.group, label)
-    when ProjectLabel then namespace_project_label_path(label.project.namespace, label.project, label)
+    when ProjectLabel then project_label_path(label.project, label)
     end
   end
 
@@ -127,27 +127,34 @@ module LabelsHelper
     project = @target_project || @project
 
     if project
-      namespace_project_labels_path(project.namespace, project, :json)
+      project_labels_path(project, :json)
     else
       dashboard_labels_path(:json)
     end
   end
 
+  def can_subscribe_to_label_in_different_levels?(label)
+    defined?(@project) && label.is_a?(GroupLabel)
+  end
+
   def label_subscription_status(label, project)
-    return 'project-level' if label.subscribed?(current_user, project)
     return 'group-level' if label.subscribed?(current_user)
+    return 'project-level' if label.subscribed?(current_user, project)
 
     'unsubscribed'
   end
 
-  def group_label_unsubscribe_path(label, project)
+  def toggle_subscription_label_path(label, project)
+    return toggle_subscription_group_label_path(label.group, label) unless project
+
     case label_subscription_status(label, project)
-    when 'project-level' then toggle_subscription_namespace_project_label_path(@project.namespace, @project, label)
     when 'group-level' then toggle_subscription_group_label_path(label.group, label)
+    when 'project-level' then toggle_subscription_project_label_path(project, label)
+    when 'unsubscribed' then toggle_subscription_project_label_path(project, label)
     end
   end
 
-  def label_subscription_toggle_button_text(label, project)
+  def label_subscription_toggle_button_text(label, project = nil)
     label.subscribed?(current_user, project) ? 'Unsubscribe' : 'Subscribe'
   end
 
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index 39d30631646e850a99a6e7c45c9ce5fac04b6e6e..78cf7b26a311bed689b0f289603b86672d29b066 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -1,8 +1,7 @@
 module MergeRequestsHelper
   def new_mr_path_from_push_event(event)
     target_project = event.project.default_merge_request_target
-    new_namespace_project_merge_request_path(
-      event.project.namespace,
+    project_new_merge_request_path(
       event.project,
       new_mr_from_push_event(event, target_project)
     )
@@ -48,8 +47,8 @@ module MergeRequestsHelper
   end
 
   def mr_change_branches_path(merge_request)
-    new_namespace_project_merge_request_path(
-      @project.namespace, @project,
+    project_new_merge_request_path(
+      @project,
       merge_request: {
         source_project_id: merge_request.source_project_id,
         target_project_id: merge_request.target_project_id,
@@ -82,9 +81,7 @@ module MergeRequestsHelper
   end
 
   def merge_request_version_path(project, merge_request, merge_request_diff, start_sha = nil)
-    diffs_namespace_project_merge_request_path(
-      project.namespace, project, merge_request,
-      diff_id: merge_request_diff.id, start_sha: start_sha)
+    diffs_project_merge_request_path(project, merge_request, diff_id: merge_request_diff.id, start_sha: start_sha)
   end
 
   def version_index(merge_request_diff)
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
index a230db22fa2490d61b08a70ad341a8c8ca0b479d..8c7851dcfc21caf67f352e315ad9454217b411d5 100644
--- a/app/helpers/milestones_helper.rb
+++ b/app/helpers/milestones_helper.rb
@@ -1,7 +1,7 @@
 module MilestonesHelper
   def milestones_filter_path(opts = {})
     if @project
-      namespace_project_milestones_path(@project.namespace, @project, opts)
+      project_milestones_path(@project, opts)
     elsif @group
       group_milestones_path(@group, opts)
     else
@@ -11,7 +11,7 @@ module MilestonesHelper
 
   def milestones_label_path(opts = {})
     if @project
-      namespace_project_issues_path(@project.namespace, @project, opts)
+      project_issues_path(@project, opts)
     elsif @group
       issues_group_path(@group, opts)
     else
@@ -73,7 +73,9 @@ module MilestonesHelper
   def milestones_filter_dropdown_path
     project = @target_project || @project
     if project
-      namespace_project_milestones_path(project.namespace, project, :json)
+      project_milestones_path(project, :json)
+    elsif @group
+      group_milestones_path(@group, :json)
     else
       dashboard_milestones_path(:json)
     end
@@ -118,7 +120,7 @@ module MilestonesHelper
 
   def milestone_merge_request_tab_path(milestone)
     if @project
-      merge_requests_namespace_project_milestone_path(@project.namespace, @project, milestone, format: :json)
+      merge_requests_project_milestone_path(@project, milestone, format: :json)
     elsif @group
       merge_requests_group_milestone_path(@group, milestone.safe_title, title: milestone.title, format: :json)
     else
@@ -128,7 +130,7 @@ module MilestonesHelper
 
   def milestone_participants_tab_path(milestone)
     if @project
-      participants_namespace_project_milestone_path(@project.namespace, @project, milestone, format: :json)
+      participants_project_milestone_path(@project, milestone, format: :json)
     elsif @group
       participants_group_milestone_path(@group, milestone.safe_title, title: milestone.title, format: :json)
     else
@@ -138,7 +140,7 @@ module MilestonesHelper
 
   def milestone_labels_tab_path(milestone)
     if @project
-      labels_namespace_project_milestone_path(@project.namespace, @project, milestone, format: :json)
+      labels_project_milestone_path(@project, milestone, format: :json)
     elsif @group
       labels_group_milestone_path(@group, milestone.safe_title, title: milestone.title, format: :json)
     else
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
index 833d3c36b28a5f19e5a6ec709d0e610c66f49aaf..e589ed4e56dc24ada470ef4415fcefa4cb146d3d 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -1,11 +1,7 @@
 module NavHelper
   def page_gutter_class
     if current_path?('merge_requests#show') ||
-        current_path?('merge_requests#diffs') ||
-        current_path?('merge_requests#commits') ||
-        current_path?('merge_requests#builds') ||
-        current_path?('merge_requests#conflicts') ||
-        current_path?('merge_requests#pipelines') ||
+        current_path?('projects/merge_requests/conflicts#show') ||
         current_path?('issues#show') ||
         current_path?('milestones#show')
       if cookies[:collapsed_gutter] == 'true'
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index c59d8dafc83308108ba488f8496b37548b47d692..0a0881d95cf70617050fc23639327a27e2d1174c 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -10,8 +10,8 @@ module NotesHelper
     Ability.can_edit_note?(current_user, note)
   end
 
-  def note_supports_slash_commands?(note)
-    Notes::SlashCommandsService.supported?(note, current_user)
+  def note_supports_quick_actions?(note)
+    Notes::QuickActionsService.supported?(note, current_user)
   end
 
   def noteable_json(noteable)
@@ -47,6 +47,18 @@ module NotesHelper
     data
   end
 
+  def add_diff_note_button(line_code, position, line_type)
+    return if @diff_notes_disabled
+
+    button_tag '',
+      class: 'add-diff-note js-add-diff-note-button',
+      type: 'submit', name: 'button',
+      data: diff_view_line_data(line_code, position, line_type),
+      title: 'Add a comment to this line' do
+      icon('comment-o')
+    end
+  end
+
   def link_to_reply_discussion(discussion, line_type = nil)
     return unless current_user
 
@@ -69,11 +81,11 @@ module NotesHelper
 
       path_params = version_params.merge(anchor: discussion.line_code)
 
-      diffs_namespace_project_merge_request_path(discussion.project.namespace, discussion.project, discussion.noteable, path_params)
+      diffs_project_merge_request_path(discussion.project, discussion.noteable, path_params)
     elsif discussion.for_commit?
       anchor = discussion.line_code if discussion.diff_discussion?
 
-      namespace_project_commit_path(discussion.project.namespace, discussion.project, discussion.noteable, anchor: anchor)
+      project_commit_path(discussion.project, discussion.noteable, anchor: anchor)
     end
   end
 
@@ -81,12 +93,7 @@ module NotesHelper
     if @snippet.is_a?(PersonalSnippet)
       snippet_notes_path(@snippet)
     else
-      namespace_project_noteable_notes_path(
-        namespace_id: @project.namespace,
-        project_id: @project,
-        target_id: @noteable.id,
-        target_type: @noteable.class.name.underscore
-      )
+      project_noteable_notes_path(@project, target_id: @noteable.id, target_type: @noteable.class.name.underscore)
     end
   end
 
@@ -94,7 +101,7 @@ module NotesHelper
     if note.noteable.is_a?(PersonalSnippet)
       snippet_note_path(note.noteable, note)
     else
-      namespace_project_note_path(project.namespace, project, note)
+      project_note_path(project, note)
     end
   end
 
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index c11dd49f4a7fb79065d9fbd7670ac72d4be23abd..5022b291f7fcfcbb93d488688788be7f2d447ccd 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -58,7 +58,17 @@ module ProjectsHelper
         link_to(simple_sanitize(owner.name), user_path(owner))
       end
 
-    project_link = link_to simple_sanitize(project.name), project_path(project), { class: "project-item-select-holder" }
+    project_link = link_to project_path(project), { class: "project-item-select-holder" } do
+      output =
+        if show_new_nav?
+          project_icon(project, alt: project.name, class: 'avatar-tile', width: 16, height: 16)
+        else
+          ""
+        end
+
+      output << simple_sanitize(project.name)
+      output.html_safe
+    end
 
     if current_user
       project_link << button_tag(type: 'button', class: 'dropdown-toggle-caret js-projects-dropdown-toggle', aria: { label: 'Toggle switch project dropdown' }, data: { target: '.js-dropdown-menu-projects', toggle: 'dropdown', order_by: 'last_activity_at' }) do
@@ -80,7 +90,7 @@ module ProjectsHelper
   end
 
   def remove_fork_project_message(project)
-    _("You are going to remove the fork relationship to source project %{forked_from_project}.  Are you ABSOLUTELY sure?") %
+    _("You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?") %
       { forked_from_project: @project.forked_from_project.name_with_namespace }
   end
 
@@ -151,14 +161,21 @@ module ProjectsHelper
       disabled: disabled_option
     )
 
-    content_tag(
-      :select,
-      options,
-      name: "project[project_feature_attributes][#{field}]",
-      id: "project_project_feature_attributes_#{field}",
-      class: "pull-right form-control #{repo_children_classes(field)}",
-      data: { field: field }
-    ).html_safe
+    content_tag :div, class: "select-wrapper" do
+      concat(
+        content_tag(
+          :select,
+          options,
+          name: "project[project_feature_attributes][#{field}]",
+          id: "project_project_feature_attributes_#{field}",
+          class: "pull-right form-control select-control #{repo_children_classes(field)} ",
+          data: { field: field }
+        )
+      )
+      concat(
+        icon('chevron-down')
+      )
+    end.html_safe
   end
 
   def link_to_autodeploy_doc
@@ -187,8 +204,25 @@ module ProjectsHelper
   end
 
   def load_pipeline_status(projects)
-    Gitlab::Cache::Ci::ProjectPipelineStatus.
-      load_in_batch_for_projects(projects)
+    Gitlab::Cache::Ci::ProjectPipelineStatus
+      .load_in_batch_for_projects(projects)
+  end
+
+  def show_no_ssh_key_message?
+    cookies[:hide_no_ssh_message].blank? && !current_user.hide_no_ssh_key && current_user.require_ssh_key?
+  end
+
+  def show_no_password_message?
+    cookies[:hide_no_password_message].blank? && !current_user.hide_no_password &&
+      ( current_user.require_password? || current_user.require_personal_access_token? )
+  end
+
+  def link_to_set_password
+    if current_user.require_password?
+      link_to s_('SetPasswordToCloneLink|set a password'), edit_profile_password_path
+    else
+      link_to s_('CreateTokenToCloneLink|create a personal access token'), profile_personal_access_tokens_path
+    end
   end
 
   private
@@ -313,8 +347,7 @@ module ProjectsHelper
 
   def add_special_file_path(project, file_name:, commit_message: nil, branch_name: nil, context: nil)
     commit_message ||= s_("CommitMessage|Add %{file_name}") % { file_name: file_name.downcase }
-    namespace_project_new_blob_path(
-      project.namespace,
+    project_new_blob_path(
       project,
       project.default_branch || 'master',
       file_name:      file_name,
@@ -325,8 +358,7 @@ module ProjectsHelper
   end
 
   def add_koding_stack_path(project)
-    namespace_project_new_blob_path(
-      project.namespace,
+    project_new_blob_path(
       project,
       project.default_branch || 'master',
       file_name:      '.koding.yml',
@@ -380,8 +412,7 @@ module ProjectsHelper
 
   def contribution_guide_path(project)
     if project && contribution_guide = project.repository.contribution_guide
-      namespace_project_blob_path(
-        project.namespace,
+      project_blob_path(
         project,
         tree_join(project.default_branch,
                   contribution_guide.name)
@@ -411,7 +442,7 @@ module ProjectsHelper
 
   def project_wiki_path_with_version(proj, page, version, is_newest)
     url_params = is_newest ? {} : { version_id: version }
-    namespace_project_wiki_path(proj.namespace, proj, page, url_params)
+    project_wiki_path(proj, page, url_params)
   end
 
   def project_status_css_class(status)
@@ -436,8 +467,7 @@ module ProjectsHelper
 
   def filename_path(project, filename)
     if project && blob = project.repository.send(filename)
-      namespace_project_blob_path(
-        project.namespace,
+      project_blob_path(
         project,
         tree_join(project.default_branch, blob.name)
       )
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 9c46035057f37ddfaf832f5e130461f6f1968611..8c44f4b0934264af3f7ef2cbc1822c63eca6a66b 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -67,16 +67,16 @@ module SearchHelper
       ref = @ref || @project.repository.root_ref
 
       [
-        { category: "Current Project", label: "Files",          url: namespace_project_tree_path(@project.namespace, @project, ref) },
-        { category: "Current Project", label: "Commits",        url: namespace_project_commits_path(@project.namespace, @project, ref) },
-        { category: "Current Project", label: "Network",        url: namespace_project_network_path(@project.namespace, @project, ref) },
-        { category: "Current Project", label: "Graph",          url: namespace_project_graph_path(@project.namespace, @project, ref) },
-        { category: "Current Project", label: "Issues",         url: namespace_project_issues_path(@project.namespace, @project) },
-        { category: "Current Project", label: "Merge Requests", url: namespace_project_merge_requests_path(@project.namespace, @project) },
-        { category: "Current Project", label: "Milestones",     url: namespace_project_milestones_path(@project.namespace, @project) },
-        { category: "Current Project", label: "Snippets",       url: namespace_project_snippets_path(@project.namespace, @project) },
-        { category: "Current Project", label: "Members",        url: namespace_project_settings_members_path(@project.namespace, @project) },
-        { category: "Current Project", label: "Wiki",           url: namespace_project_wikis_path(@project.namespace, @project) }
+        { category: "Current Project", label: "Files",          url: project_tree_path(@project, ref) },
+        { category: "Current Project", label: "Commits",        url: project_commits_path(@project, ref) },
+        { category: "Current Project", label: "Network",        url: project_network_path(@project, ref) },
+        { category: "Current Project", label: "Graph",          url: project_graph_path(@project, ref) },
+        { category: "Current Project", label: "Issues",         url: project_issues_path(@project) },
+        { category: "Current Project", label: "Merge Requests", url: project_merge_requests_path(@project) },
+        { category: "Current Project", label: "Milestones",     url: project_milestones_path(@project) },
+        { category: "Current Project", label: "Snippets",       url: project_snippets_path(@project) },
+        { category: "Current Project", label: "Members",        url: project_settings_members_path(@project) },
+        { category: "Current Project", label: "Wiki",           url: project_wikis_path(@project) }
       ]
     else
       []
@@ -97,14 +97,14 @@ module SearchHelper
 
   # Autocomplete results for the current user's projects
   def projects_autocomplete(term, limit = 5)
-    current_user.authorized_projects.search_by_title(term).
-      sorted_by_stars.non_archived.limit(limit).map do |p|
+    current_user.authorized_projects.search_by_title(term)
+      .sorted_by_stars.non_archived.limit(limit).map do |p|
       {
         category: "Projects",
         id: p.id,
         value: "#{search_result_sanitize(p.name)}",
         label: "#{search_result_sanitize(p.name_with_namespace)}",
-        url: namespace_project_path(p.namespace, p)
+        url: project_path(p)
       }
     end
   end
@@ -126,6 +126,18 @@ module SearchHelper
     search_path(options)
   end
 
+  def search_filter_input_options(type)
+    {
+      id: "filtered-search-#{type}",
+      placeholder: 'Search or filter results...',
+      data: {
+        'project-id' => @project.id,
+        'username-params' => @users.to_json(only: [:id, :username]),
+        'base-endpoint' => project_path(@project)
+      }
+    }
+  end
+
   # Sanitize a HTML field for search display. Most tags are stripped out and the
   # maximum length is set to 200 characters.
   def search_md_sanitize(object, field)
diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb
index 2fd64b3441e4c33b9110f8034817c8c07a452c2e..b447d4952e7d4b8021784e4a2b5c6af612194cef 100644
--- a/app/helpers/snippets_helper.rb
+++ b/app/helpers/snippets_helper.rb
@@ -1,8 +1,7 @@
 module SnippetsHelper
   def reliable_snippet_path(snippet, opts = nil)
     if snippet.project_id?
-      namespace_project_snippet_path(snippet.project.namespace,
-                                     snippet.project, snippet, opts)
+      project_snippet_path(snippet.project, snippet, opts)
     else
       snippet_path(snippet, opts)
     end
@@ -10,7 +9,7 @@ module SnippetsHelper
 
   def download_snippet_path(snippet)
     if snippet.project_id
-      raw_namespace_project_snippet_path(@project.namespace, @project, snippet, inline: false)
+      raw_project_snippet_path(@project, snippet, inline: false)
     else
       raw_snippet_path(snippet, inline: false)
     end
@@ -21,7 +20,7 @@ module SnippetsHelper
   # @returns String, path to snippet index
   def subject_snippets_path(subject = nil, opts = nil)
     if subject.is_a?(Project)
-      namespace_project_snippets_path(subject.namespace, subject, opts)
+      project_snippets_path(subject, opts)
     else # assume subject === User
       dashboard_snippets_path(opts)
     end
diff --git a/app/helpers/submodule_helper.rb b/app/helpers/submodule_helper.rb
index 8e0a1e2ecdf23e37648bf8a7c29d11db51757803..b24039fb349130a4d34e752ac46336e9cdef958a 100644
--- a/app/helpers/submodule_helper.rb
+++ b/app/helpers/submodule_helper.rb
@@ -73,6 +73,7 @@ module SubmoduleHelper
   end
 
   def relative_self_links(url, commit)
+    url.rstrip!
     # Map relative links to a namespace and project
     # For example:
     # ../bar.git -> same namespace, repo bar
diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb
index 1a55ee05996e232172812833488b2d453e2c4747..ee701076a1404a1e9b379e99615ddffb47de0ed6 100644
--- a/app/helpers/tab_helper.rb
+++ b/app/helpers/tab_helper.rb
@@ -107,8 +107,7 @@ module TabHelper
   def branches_tab_class
     if current_controller?(:protected_branches) ||
         current_controller?(:branches) ||
-        current_page?(namespace_project_repository_path(@project.namespace,
-                                                        @project))
+        current_page?(project_repository_path(@project))
       'active'
     end
   end
diff --git a/app/helpers/tags_helper.rb b/app/helpers/tags_helper.rb
index 31aaf9e5607aac967e262e55478828b8aef29d77..d000d6b1c0affe3073dd7055e028fadbc6d11c81 100644
--- a/app/helpers/tags_helper.rb
+++ b/app/helpers/tags_helper.rb
@@ -10,7 +10,7 @@ module TagsHelper
     }
 
     options = exist_opts.merge(options)
-    namespace_project_tags_path(@project.namespace, @project, @id, options)
+    project_tags_path(@project, @id, options)
   end
 
   def tag_list(project)
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index 3d1b3a4711ab64049479a71a118ffe369622e804..2a7aa299e8389129953f63640a55ec8964093e5c 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -39,7 +39,7 @@ module TodosHelper
     anchor = dom_id(todo.note) if todo.note.present?
 
     if todo.for_commit?
-      namespace_project_commit_path(todo.project.namespace.becomes(Namespace), todo.project,
+      project_commit_path(todo.project,
                                     todo.target, anchor: anchor)
     else
       path = [todo.project.namespace.becomes(Namespace), todo.project, todo.target]
diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb
index 9c623c9ba7cc1b959b9ee64a63eef87a50234ab8..b5f54d3e154411c9749988408f18a9503fe4608b 100644
--- a/app/helpers/users_helper.rb
+++ b/app/helpers/users_helper.rb
@@ -4,4 +4,14 @@ module UsersHelper
             title: user.email,
             class: 'has-tooltip commit-committer-link')
   end
+
+  def user_email_help_text(user)
+    return 'We also use email for avatar detection if no avatar is uploaded.' unless user.unconfirmed_email.present?
+
+    confirmation_link = link_to 'Resend confirmation e-mail', user_confirmation_path(user: { email: @user.unconfirmed_email }), method: :post
+
+    h('Please click the link in the confirmation email before continuing. It was sent to ') +
+      content_tag(:strong) { user.unconfirmed_email } + h('.') +
+      content_tag(:p) { confirmation_link }
+  end
 end
diff --git a/app/helpers/webpack_helper.rb b/app/helpers/webpack_helper.rb
index 6bacda9fe75153913e25ea770a344247629eb3b1..0386df223741901d61aad057e660eb0f9b4d99f3 100644
--- a/app/helpers/webpack_helper.rb
+++ b/app/helpers/webpack_helper.rb
@@ -11,20 +11,29 @@ module WebpackHelper
 
     paths = Webpack::Rails::Manifest.asset_paths(source)
     if extension
-      paths = paths.select { |p| p.ends_with? ".#{extension}" }
+      paths.select! { |p| p.ends_with? ".#{extension}" }
     end
 
-    # include full webpack-dev-server url for rspec tests running locally
+    force_host = webpack_public_host
+    if force_host
+      paths.map! { |p| "#{force_host}#{p}" }
+    end
+
+    paths
+  end
+
+  def webpack_public_host
     if Rails.env.test? && Rails.configuration.webpack.dev_server.enabled
       host = Rails.configuration.webpack.dev_server.host
       port = Rails.configuration.webpack.dev_server.port
       protocol = Rails.configuration.webpack.dev_server.https ? 'https' : 'http'
-
-      paths.map! do |p|
-        "#{protocol}://#{host}:#{port}#{p}"
-      end
+      "#{protocol}://#{host}:#{port}"
+    else
+      ActionController::Base.asset_host.try(:chomp, '/')
     end
+  end
 
-    paths
+  def webpack_public_path
+    "#{webpack_public_host}/#{Rails.application.config.webpack.public_path}/"
   end
 end
diff --git a/app/helpers/wiki_helper.rb b/app/helpers/wiki_helper.rb
index 3e3f6246fc5ef033f939ffd18afc6463bfbbbfea..99212a3438f7f5415a4239bfc2924c763e187fe8 100644
--- a/app/helpers/wiki_helper.rb
+++ b/app/helpers/wiki_helper.rb
@@ -6,8 +6,8 @@ module WikiHelper
   # Returns a String composed of the capitalized name of each directory and the
   # capitalized name of the page itself.
   def breadcrumb(page_slug)
-    page_slug.split('/').
-      map { |dir_or_page| WikiPage.unhyphenize(dir_or_page).capitalize }.
-      join(' / ')
+    page_slug.split('/')
+      .map { |dir_or_page| WikiPage.unhyphenize(dir_or_page).capitalize }
+      .join(' / ')
   end
 end
diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb
index 0f84784129511bc8843c6b45dc3801b8563010b0..64ca2d2eacf95d293126bee57026a3c8ba47eb5f 100644
--- a/app/mailers/emails/issues.rb
+++ b/app/mailers/emails/issues.rb
@@ -31,7 +31,7 @@ module Emails
       setup_issue_mail(issue_id, recipient_id)
 
       @label_names = label_names
-      @labels_url = namespace_project_labels_url(@project.namespace, @project)
+      @labels_url = project_labels_url(@project)
       mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id))
     end
 
@@ -56,7 +56,7 @@ module Emails
     def setup_issue_mail(issue_id, recipient_id)
       @issue = Issue.find(issue_id)
       @project = @issue.project
-      @target_url = namespace_project_issue_url(@project.namespace, @project, @issue)
+      @target_url = project_issue_url(@project, @issue)
 
       @sent_notification = SentNotification.record(@issue, recipient_id, reply_key)
     end
diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb
index ec27ac517db361dd62693c29cc68168500168abd..3626f8ce416131dd2a2716c98757c142cdc172c5 100644
--- a/app/mailers/emails/merge_requests.rb
+++ b/app/mailers/emails/merge_requests.rb
@@ -22,7 +22,7 @@ module Emails
       setup_merge_request_mail(merge_request_id, recipient_id)
 
       @label_names = label_names
-      @labels_url = namespace_project_labels_url(@project.namespace, @project)
+      @labels_url = project_labels_url(@project)
       mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id))
     end
 
@@ -59,7 +59,7 @@ module Emails
     def setup_merge_request_mail(merge_request_id, recipient_id)
       @merge_request = MergeRequest.find(merge_request_id)
       @project = @merge_request.project
-      @target_url = namespace_project_merge_request_url(@project.namespace, @project, @merge_request)
+      @target_url = project_merge_request_url(@project, @merge_request)
 
       @sent_notification = SentNotification.record(@merge_request, recipient_id, reply_key)
     end
diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb
index 00707a0023eefc4868b6565c9d31d6ceef07e9e8..77a82b895cea293604bb89da16f9ca0f4594c443 100644
--- a/app/mailers/emails/notes.rb
+++ b/app/mailers/emails/notes.rb
@@ -4,7 +4,7 @@ module Emails
       setup_note_mail(note_id, recipient_id)
 
       @commit = @note.noteable
-      @target_url = namespace_project_commit_url(*note_target_url_options)
+      @target_url = project_commit_url(*note_target_url_options)
       mail_answer_thread(@commit, note_thread_options(recipient_id))
     end
 
@@ -12,7 +12,7 @@ module Emails
       setup_note_mail(note_id, recipient_id)
 
       @issue = @note.noteable
-      @target_url = namespace_project_issue_url(*note_target_url_options)
+      @target_url = project_issue_url(*note_target_url_options)
       mail_answer_thread(@issue, note_thread_options(recipient_id))
     end
 
@@ -20,7 +20,7 @@ module Emails
       setup_note_mail(note_id, recipient_id)
 
       @merge_request = @note.noteable
-      @target_url = namespace_project_merge_request_url(*note_target_url_options)
+      @target_url = project_merge_request_url(*note_target_url_options)
       mail_answer_thread(@merge_request, note_thread_options(recipient_id))
     end
 
@@ -28,7 +28,7 @@ module Emails
       setup_note_mail(note_id, recipient_id)
 
       @snippet = @note.noteable
-      @target_url = namespace_project_snippet_url(*note_target_url_options)
+      @target_url = project_snippet_url(*note_target_url_options)
       mail_answer_thread(@snippet, note_thread_options(recipient_id))
     end
 
@@ -43,7 +43,7 @@ module Emails
     private
 
     def note_target_url_options
-      [@project.namespace, @project, @note.noteable, anchor: "note_#{@note.id}"]
+      [@project, @note.noteable, anchor: "note_#{@note.id}"]
     end
 
     def note_thread_options(recipient_id)
diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb
index e0af70814114e8fed0136ae01d78ba9caf8eb58e..761d873c01c48b512a7ce4f95de423715fc729ae 100644
--- a/app/mailers/emails/projects.rb
+++ b/app/mailers/emails/projects.rb
@@ -3,7 +3,7 @@ module Emails
     def project_was_moved_email(project_id, user_id, old_path_with_namespace)
       @current_user = @user = User.find user_id
       @project = Project.find project_id
-      @target_url = namespace_project_url(@project.namespace, @project)
+      @target_url = project_url(@project)
       @old_path_with_namespace = old_path_with_namespace
       mail(to: @user.notification_email,
            subject: subject("Project was moved"))
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index f315e38bcaa308fd51b9ff492189280711950611..eaac6fcb548aac34a42da1ee4152c574f1edf79e 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -1,5 +1,6 @@
 class Notify < BaseMailer
   include ActionDispatch::Routing::PolymorphicRoutes
+  include GitlabRoutingHelper
 
   include Emails::Issues
   include Emails::MergeRequests
diff --git a/app/models/ability.rb b/app/models/ability.rb
index f3692a5a06764f60a1a28ed1ee6bcfdc64dd751b..0b6bcbde5d94aa639a3a147d923d741eb2748eca 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -1,35 +1,20 @@
+require_dependency 'declarative_policy'
+
 class Ability
   class << self
     # Given a list of users and a project this method returns the users that can
     # read the given project.
     def users_that_can_read_project(users, project)
-      if project.public?
-        users
-      else
-        users.select do |user|
-          if user.admin?
-            true
-          elsif project.internal? && !user.external?
-            true
-          elsif project.owner == user
-            true
-          elsif project.team.members.include?(user)
-            true
-          else
-            false
-          end
-        end
+      DeclarativePolicy.subject_scope do
+        users.select { |u| allowed?(u, :read_project, project) }
       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] : []
+      DeclarativePolicy.subject_scope do
+        users.select { |u| allowed?(u, :read_personal_snippet, snippet) }
       end
     end
 
@@ -38,42 +23,35 @@ class Ability
     # issues - The issues to reduce down to those readable by the user.
     # user - The User for which to check the issues
     def issues_readable_by_user(issues, user = nil)
-      return issues if user && user.admin?
-
-      issues.select { |issue| issue.visible_to_user?(user) }
+      DeclarativePolicy.user_scope do
+        issues.select { |issue| issue.visible_to_user?(user) }
+      end
     end
 
-    # TODO: make this private and use the actual abilities stuff for this
     def can_edit_note?(user, note)
-      return false if !note.editable? || !user.present?
-      return true if note.author == user || user.admin?
-
-      if note.project
-        max_access_level = note.project.team.max_member_access(user.id)
-        max_access_level >= Gitlab::Access::MASTER
-      else
-        false
-      end
+      allowed?(user, :edit_note, note)
     end
 
-    def allowed?(user, action, subject = :global)
-      allowed(user, subject).include?(action)
-    end
+    def allowed?(user, action, subject = :global, opts = {})
+      if subject.is_a?(Hash)
+        opts, subject = subject, :global
+      end
 
-    def allowed(user, subject = :global)
-      return BasePolicy::RuleSet.none if subject.nil?
-      return uncached_allowed(user, subject) unless RequestStore.active?
+      policy = policy_for(user, subject)
 
-      user_key = user ? user.id : 'anonymous'
-      subject_key = subject == :global ? 'global' : "#{subject.class.name}/#{subject.id}"
-      key = "/ability/#{user_key}/#{subject_key}"
-      RequestStore[key] ||= uncached_allowed(user, subject).freeze
+      case opts[:scope]
+      when :user
+        DeclarativePolicy.user_scope { policy.can?(action) }
+      when :subject
+        DeclarativePolicy.subject_scope { policy.can?(action) }
+      else
+        policy.can?(action)
+      end
     end
 
-    private
-
-    def uncached_allowed(user, subject)
-      BasePolicy.class_for(subject).abilities(user, subject)
+    def policy_for(user, subject = :global)
+      cache = RequestStore.active? ? RequestStore : {}
+      DeclarativePolicy.policy_for(user, subject, cache: cache)
     end
   end
 end
diff --git a/app/models/award_emoji.rb b/app/models/award_emoji.rb
index ebe6044160344bc28309ba59d9df32b6bda45d1d..91b62dabbcd94a942fc17a50f48fbdb1b91ea1e5 100644
--- a/app/models/award_emoji.rb
+++ b/app/models/award_emoji.rb
@@ -19,9 +19,9 @@ class AwardEmoji < ActiveRecord::Base
 
   class << self
     def votes_for_collection(ids, type)
-      select('name', 'awardable_id', 'COUNT(*) as count').
-        where('name IN (?) AND awardable_type = ? AND awardable_id IN (?)', [DOWNVOTE_NAME, UPVOTE_NAME], type, ids).
-        group('name', 'awardable_id')
+      select('name', 'awardable_id', 'COUNT(*) as count')
+        .where('name IN (?) AND awardable_type = ? AND awardable_id IN (?)', [DOWNVOTE_NAME, UPVOTE_NAME], type, ids)
+        .group('name', 'awardable_id')
     end
   end
 
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 58758f7ca8a940dae1b386bf55f120d67e5d7186..2e7a80d308b360aff626821b288e6f5126f25683 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -138,17 +138,6 @@ module Ci
       ExpandVariables.expand(environment, simple_variables) if environment
     end
 
-    def environment_url
-      return @environment_url if defined?(@environment_url)
-
-      @environment_url =
-        if unexpanded_url = options&.dig(:environment, :url)
-          ExpandVariables.expand(unexpanded_url, simple_variables)
-        else
-          persisted_environment&.external_url
-        end
-    end
-
     def has_environment?
       environment.present?
     end
@@ -187,12 +176,15 @@ module Ci
     #   * Lowercased
     #   * Anything not matching [a-z0-9-] is replaced with a -
     #   * Maximum length is 63 bytes
+    #   * First/Last Character is not a hyphen
     def ref_slug
-      slugified = ref.to_s.downcase
-      slugified.gsub(/[^a-z0-9]/, '-')[0..62]
+      ref.to_s
+          .downcase
+          .gsub(/[^a-z0-9]/, '-')[0..62]
+          .gsub(/(\A-+|-+\z)/, '')
     end
 
-    # Variables whose value does not depend on other variables
+    # Variables whose value does not depend on environment
     def simple_variables
       variables = predefined_variables
       variables += project.predefined_variables
@@ -207,7 +199,8 @@ module Ci
       variables
     end
 
-    # All variables, including those dependent on other variables
+    # All variables, including those dependent on environment, which could
+    # contain unexpanded variables.
     def variables
       simple_variables.concat(persisted_environment_variables)
     end
@@ -481,9 +474,10 @@ module Ci
 
       variables = persisted_environment.predefined_variables
 
-      if url = environment_url
-        variables << { key: 'CI_ENVIRONMENT_URL', value: url, public: true }
-      end
+      # Here we're passing unexpanded environment_url for runner to expand,
+      # and we need to make sure that CI_ENVIRONMENT_NAME and
+      # CI_ENVIRONMENT_SLUG so on are available for the URL be expanded.
+      variables << { key: 'CI_ENVIRONMENT_URL', value: environment_url, public: true } if environment_url
 
       variables
     end
@@ -506,6 +500,10 @@ module Ci
       variables
     end
 
+    def environment_url
+      options&.dig(:environment, :url) || persisted_environment&.external_url
+    end
+
     def build_attributes_from_config
       return {} unless pipeline.config_processor
 
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 9ddecba518390fe5a36072dc93612d473d523a4c..c5847dee7f7e79ada66fe67026bdaa4a8ccfd7c5 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -140,6 +140,7 @@ module Ci
         where(id: max_id)
       end
     end
+    scope :internal, -> { where(source: internal_sources) }
 
     def self.latest_status(ref = nil)
       latest(ref).status
@@ -163,13 +164,17 @@ module Ci
       where.not(duration: nil).sum(:duration)
     end
 
+    def self.internal_sources
+      sources.reject { |source| source == "external" }.values
+    end
+
     def stages_count
       statuses.select(:stage).distinct.count
     end
 
     def stages_names
-      statuses.order(:stage_idx).distinct.
-        pluck(:stage, :stage_idx).map(&:first)
+      statuses.order(:stage_idx).distinct
+        .pluck(:stage, :stage_idx).map(&:first)
     end
 
     def legacy_stage(name)
@@ -321,10 +326,24 @@ module Ci
       end
     end
 
+    def ci_yaml_file_path
+      if project.ci_config_path.blank?
+        '.gitlab-ci.yml'
+      else
+        project.ci_config_path
+      end
+    end
+
     def ci_yaml_file
       return @ci_yaml_file if defined?(@ci_yaml_file)
 
-      @ci_yaml_file = project.repository.gitlab_ci_yml_for(sha) rescue nil
+      @ci_yaml_file = begin
+        project.repository.gitlab_ci_yml_for(sha, ci_yaml_file_path)
+      rescue Rugged::ReferenceError, GRPC::NotFound, GRPC::Internal
+        self.yaml_errors =
+          "Failed to load CI/CD config file at #{ci_yaml_file_path}"
+        nil
+      end
     end
 
     def has_yaml_errors?
@@ -372,7 +391,8 @@ module Ci
 
     def predefined_variables
       [
-        { key: 'CI_PIPELINE_ID', value: id.to_s, public: true }
+        { key: 'CI_PIPELINE_ID', value: id.to_s, public: true },
+        { key: 'CI_CONFIG_PATH', value: ci_yaml_file_path, public: true }
       ]
     end
 
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 487ba61bc9c7019618cf8a25f5c60961c8b3761f..d12f96f3d0bbf85f9d34635fb29f6916d7d912fb 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -30,8 +30,8 @@ module Ci
     scope :assignable_for, ->(project) do
       # FIXME: That `to_sql` is needed to workaround a weird Rails bug.
       #        Without that, placeholders would miss one and couldn't match.
-      where(locked: false).
-        where.not("id IN (#{project.runners.select(:id).to_sql})").specific
+      where(locked: false)
+        .where.not("id IN (#{project.runners.select(:id).to_sql})").specific
     end
 
     validate :tag_constraints
diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb
index f235260208fe41eb3b5718fa9ef0f9e738c6954b..0b8d0ff881a2b252f4bdc888153fde376431b74e 100644
--- a/app/models/ci/variable.rb
+++ b/app/models/ci/variable.rb
@@ -1,27 +1,12 @@
 module Ci
   class Variable < ActiveRecord::Base
     extend Ci::Model
+    include HasVariable
 
     belongs_to :project
 
-    validates :key,
-      presence: true,
-      uniqueness: { scope: :project_id },
-      length: { maximum: 255 },
-      format: { with: /\A[a-zA-Z0-9_]+\z/,
-                message: "can contain only letters, digits and '_'." }
+    validates :key, uniqueness: { scope: [:project_id, :environment_scope] }
 
-    scope :order_key_asc, -> { reorder(key: :asc) }
     scope :unprotected, -> { where(protected: false) }
-
-    attr_encrypted :value,
-       mode: :per_attribute_iv_and_salt,
-       insecure_mode: true,
-       key: Gitlab::Application.secrets.db_key_base,
-       algorithm: 'aes-256-cbc'
-
-    def to_runner_variable
-      { key: key, value: value, public: false }
-    end
   end
 end
diff --git a/app/models/concerns/feature_gate.rb b/app/models/concerns/feature_gate.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5db64fe82c4a3e4e9a213e57f9781bb62589df82
--- /dev/null
+++ b/app/models/concerns/feature_gate.rb
@@ -0,0 +1,7 @@
+module FeatureGate
+  def flipper_id
+    return nil if new_record?
+
+    "#{self.class.name}:#{id}"
+  end
+end
diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb
index 3c9c6584e026ea7ecb8b7fd6c8277a8e2e92e505..32af55661358da34d89e00307a0c2c544a197186 100644
--- a/app/models/concerns/has_status.rb
+++ b/app/models/concerns/has_status.rb
@@ -11,18 +11,21 @@ module HasStatus
 
   class_methods do
     def status_sql
-      scope = respond_to?(:exclude_ignored) ? exclude_ignored : all
-
-      builds = scope.select('count(*)').to_sql
-      created = scope.created.select('count(*)').to_sql
-      success = scope.success.select('count(*)').to_sql
-      manual = scope.manual.select('count(*)').to_sql
-      pending = scope.pending.select('count(*)').to_sql
-      running = scope.running.select('count(*)').to_sql
-      skipped = scope.skipped.select('count(*)').to_sql
-      canceled = scope.canceled.select('count(*)').to_sql
+      scope_relevant = respond_to?(:exclude_ignored) ? exclude_ignored : all
+      scope_warnings = respond_to?(:failed_but_allowed) ? failed_but_allowed : none
+
+      builds = scope_relevant.select('count(*)').to_sql
+      created = scope_relevant.created.select('count(*)').to_sql
+      success = scope_relevant.success.select('count(*)').to_sql
+      manual = scope_relevant.manual.select('count(*)').to_sql
+      pending = scope_relevant.pending.select('count(*)').to_sql
+      running = scope_relevant.running.select('count(*)').to_sql
+      skipped = scope_relevant.skipped.select('count(*)').to_sql
+      canceled = scope_relevant.canceled.select('count(*)').to_sql
+      warnings = scope_warnings.select('count(*) > 0').to_sql.presence || 'false'
 
       "(CASE
+        WHEN (#{builds})=(#{skipped}) AND (#{warnings}) THEN 'success'
         WHEN (#{builds})=(#{skipped}) THEN 'skipped'
         WHEN (#{builds})=(#{success}) THEN 'success'
         WHEN (#{builds})=(#{created}) THEN 'created'
diff --git a/app/models/concerns/has_variable.rb b/app/models/concerns/has_variable.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9585b5583dc97303b9ec5d8ef1dd0f9be0f00cec
--- /dev/null
+++ b/app/models/concerns/has_variable.rb
@@ -0,0 +1,23 @@
+module HasVariable
+  extend ActiveSupport::Concern
+
+  included do
+    validates :key,
+      presence: true,
+      length: { maximum: 255 },
+      format: { with: /\A[a-zA-Z0-9_]+\z/,
+                message: "can contain only letters, digits and '_'." }
+
+    scope :order_key_asc, -> { reorder(key: :asc) }
+
+    attr_encrypted :value,
+       mode: :per_attribute_iv_and_salt,
+       insecure_mode: true,
+       key: Gitlab::Application.secrets.db_key_base,
+       algorithm: 'aes-256-cbc'
+
+    def to_runner_variable
+      { key: key, value: value, public: false }
+    end
+  end
+end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index ea10d004c9c9ede49d1a71c71ccfb8f5bb0dedc6..41c8b52527310b3fd7d6756ed5fae347cf3de177 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -67,7 +67,6 @@ module Issuable
 
     scope :authored, ->(user) { where(author_id: user) }
     scope :recent, -> { reorder(id: :desc) }
-    scope :order_position_asc, -> { reorder(position: :asc) }
     scope :of_projects, ->(ids) { where(project_id: ids) }
     scope :of_milestones, ->(ids) { where(milestone_id: ids) }
     scope :with_milestone, ->(title) { left_joins_milestones.where(milestones: { title: title }) }
@@ -103,6 +102,14 @@ module Issuable
     def locking_enabled?
       title_changed? || description_changed?
     end
+
+    def allows_multiple_assignees?
+      false
+    end
+
+    def has_multiple_assignees?
+      assignees.count > 1
+    end
   end
 
   module ClassMethods
@@ -139,7 +146,6 @@ module Issuable
                when 'upvotes_desc' then order_upvotes_desc
                when 'label_priority' then order_labels_priority(excluded_labels: excluded_labels)
                when 'priority' then order_due_date_and_labels_priority(excluded_labels: excluded_labels)
-               when 'position_asc' then  order_position_asc
                else
                  order_by(method)
                end
@@ -163,9 +169,9 @@ module Issuable
       #
       milestones_due_date = 'MIN(milestones.due_date)'
 
-      order_milestone_due_asc.
-        order_labels_priority(excluded_labels: excluded_labels, extra_select_columns: [milestones_due_date]).
-        reorder(Gitlab::Database.nulls_last_order(milestones_due_date, 'ASC'),
+      order_milestone_due_asc
+        .order_labels_priority(excluded_labels: excluded_labels, extra_select_columns: [milestones_due_date])
+        .reorder(Gitlab::Database.nulls_last_order(milestones_due_date, 'ASC'),
                 Gitlab::Database.nulls_last_order('highest_priority', 'ASC'))
     end
 
@@ -184,9 +190,9 @@ module Issuable
         "(#{highest_priority}) AS highest_priority"
       ] + extra_select_columns
 
-      select(select_columns.join(', ')).
-        group(arel_table[:id]).
-        reorder(Gitlab::Database.nulls_last_order('highest_priority', 'ASC'))
+      select(select_columns.join(', '))
+        .group(arel_table[:id])
+        .reorder(Gitlab::Database.nulls_last_order('highest_priority', 'ASC'))
     end
 
     def with_label(title, sort = nil)
diff --git a/app/models/concerns/mentionable/reference_regexes.rb b/app/models/concerns/mentionable/reference_regexes.rb
index 1848230ec7e0b4f15993958f641082249230e617..2d86a70c395e5a40328f5eb80376aabfc425d4f2 100644
--- a/app/models/concerns/mentionable/reference_regexes.rb
+++ b/app/models/concerns/mentionable/reference_regexes.rb
@@ -14,7 +14,7 @@ module Mentionable
     end
 
     EXTERNAL_PATTERN = begin
-      issue_pattern = ExternalIssue.reference_pattern
+      issue_pattern = IssueTrackerService.reference_pattern
       link_patterns = URI.regexp(%w(http https))
       reference_pattern(link_patterns, issue_pattern)
     end
diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb
index a3472af5c55c954e721d29bf9030b62f75b30662..01599ce49c64250116997aa5ff9c3c50b779f092 100644
--- a/app/models/concerns/milestoneish.rb
+++ b/app/models/concerns/milestoneish.rb
@@ -40,10 +40,18 @@ module Milestoneish
   def issues_visible_to_user(user)
     memoize_per_user(user, :issues_visible_to_user) do
       IssuesFinder.new(user, issues_finder_params)
-        .execute.includes(:assignees).where(milestone_id: milestoneish_ids)
+        .execute.preload(:assignees).where(milestone_id: milestoneish_ids)
     end
   end
 
+  def sorted_issues(user)
+    issues_visible_to_user(user).preload_associations.sort('label_priority')
+  end
+
+  def sorted_merge_requests
+    merge_requests.sort('label_priority')
+  end
+
   def upcoming?
     start_date && start_date.future?
   end
diff --git a/app/models/concerns/relative_positioning.rb b/app/models/concerns/relative_positioning.rb
index f1d8532a6d6f5e2dd664dbae8ecdf4555c20f735..7cb9a28a2843c6951ec76c672e95e69ad80d3501 100644
--- a/app/models/concerns/relative_positioning.rb
+++ b/app/models/concerns/relative_positioning.rb
@@ -18,10 +18,10 @@ module RelativePositioning
     prev_pos = nil
 
     if self.relative_position
-      prev_pos = self.class.
-        in_projects(project.id).
-        where('relative_position < ?', self.relative_position).
-        maximum(:relative_position)
+      prev_pos = self.class
+        .in_projects(project.id)
+        .where('relative_position < ?', self.relative_position)
+        .maximum(:relative_position)
     end
 
     prev_pos
@@ -31,10 +31,10 @@ module RelativePositioning
     next_pos = nil
 
     if self.relative_position
-      next_pos = self.class.
-        in_projects(project.id).
-        where('relative_position > ?', self.relative_position).
-        minimum(:relative_position)
+      next_pos = self.class
+        .in_projects(project.id)
+        .where('relative_position > ?', self.relative_position)
+        .minimum(:relative_position)
     end
 
     next_pos
diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb
index 63d02b76f6b67b5768f3169ba5e15c1c476506ed..ee108f010a69052cee07c6fd41cf81ac48caffbb 100644
--- a/app/models/concerns/routable.rb
+++ b/app/models/concerns/routable.rb
@@ -103,8 +103,20 @@ module Routable
   def full_path
     return uncached_full_path unless RequestStore.active?
 
-    key = "routable/full_path/#{self.class.name}/#{self.id}"
-    RequestStore[key] ||= uncached_full_path
+    RequestStore[full_path_key] ||= uncached_full_path
+  end
+
+  def expires_full_path_cache
+    RequestStore.delete(full_path_key) if RequestStore.active?
+    @full_path = nil
+  end
+
+  def build_full_path
+    if parent && path
+      parent.full_path + '/' + path
+    else
+      path
+    end
   end
 
   private
@@ -127,6 +139,10 @@ module Routable
     path_changed? || parent_changed?
   end
 
+  def full_path_key
+    @full_path_key ||= "routable/full_path/#{self.class.name}/#{self.id}"
+  end
+
   def build_full_name
     if parent && name
       parent.human_name + ' / ' + name
@@ -135,14 +151,6 @@ module Routable
     end
   end
 
-  def build_full_path
-    if parent && path
-      parent.full_path + '/' + path
-    else
-      path
-    end
-  end
-
   def update_route
     prepare_route
     route.save
diff --git a/app/models/concerns/sha_attribute.rb b/app/models/concerns/sha_attribute.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c28974a3cdfed941bb940f23743ad45e67e02dd1
--- /dev/null
+++ b/app/models/concerns/sha_attribute.rb
@@ -0,0 +1,18 @@
+module ShaAttribute
+  extend ActiveSupport::Concern
+
+  module ClassMethods
+    def sha_attribute(name)
+      column = columns.find { |c| c.name == name.to_s }
+
+      # In case the table doesn't exist we won't be able to find the column,
+      # thus we will only check the type if the column is present.
+      if column && column.type != :binary
+        raise ArgumentError,
+          "sha_attribute #{name.inspect} is invalid since the column type is not :binary"
+      end
+
+      attribute(name, Gitlab::Database::ShaAttribute.new)
+    end
+  end
+end
diff --git a/app/models/concerns/sortable.rb b/app/models/concerns/sortable.rb
index b9a2d812edd429e9d3b3d4af0d2eacc2c0812921..fdacfa5a194fe8242f4f57df5df2ba196eaba34b 100644
--- a/app/models/concerns/sortable.rb
+++ b/app/models/concerns/sortable.rb
@@ -5,6 +5,25 @@
 module Sortable
   extend ActiveSupport::Concern
 
+  module DropDefaultScopeOnFinders
+    # Override these methods to drop the `ORDER BY id DESC` default scope.
+    # See http://dba.stackexchange.com/a/110919 for why we do this.
+    %i[find find_by find_by!].each do |meth|
+      define_method meth do |*args, &block|
+        return super(*args, &block) if block
+
+        unordered_relation = unscope(:order)
+
+        # We cannot simply call `meth` on `unscope(:order)`, since that is also
+        # an instance of the same relation class this module is included into,
+        # which means we'd get infinite recursion.
+        # We explicitly use the original implementation to prevent this.
+        original_impl = method(__method__).super_method.unbind
+        original_impl.bind(unordered_relation).call(*args)
+      end
+    end
+  end
+
   included do
     # By default all models should be ordered
     # by created_at field starting from newest
@@ -18,6 +37,10 @@ module Sortable
     scope :order_updated_asc, -> { reorder(updated_at: :asc) }
     scope :order_name_asc, -> { reorder(name: :asc) }
     scope :order_name_desc, -> { reorder(name: :desc) }
+
+    # All queries (relations) on this model are instances of this `relation_klass`.
+    relation_klass = relation_delegate_class(ActiveRecord::Relation)
+    relation_klass.prepend DropDefaultScopeOnFinders
   end
 
   module ClassMethods
@@ -39,12 +62,12 @@ module Sortable
     private
 
     def highest_label_priority(target_type_column: nil, target_type: nil, target_column:, project_column:, excluded_labels: [])
-      query = Label.select(LabelPriority.arel_table[:priority].minimum).
-        left_join_priorities.
-        joins(:label_links).
-        where("label_priorities.project_id = #{project_column}").
-        where("label_links.target_id = #{target_column}").
-        reorder(nil)
+      query = Label.select(LabelPriority.arel_table[:priority].minimum)
+        .left_join_priorities
+        .joins(:label_links)
+        .where("label_priorities.project_id = #{project_column}")
+        .where("label_links.target_id = #{target_column}")
+        .reorder(nil)
 
       query =
         if target_type_column
diff --git a/app/models/concerns/subscribable.rb b/app/models/concerns/subscribable.rb
index 83daa9b1a64c87c376b3dec4c36d4b67f5cd2cdb..f60a0f8f438eacd3e3b9b53173bc809d5cdfd324 100644
--- a/app/models/concerns/subscribable.rb
+++ b/app/models/concerns/subscribable.rb
@@ -27,16 +27,16 @@ module Subscribable
   end
 
   def subscribers(project)
-    subscriptions_available(project).
-      where(subscribed: true).
-      map(&:user)
+    subscriptions_available(project)
+      .where(subscribed: true)
+      .map(&:user)
   end
 
   def toggle_subscription(user, project = nil)
     unsubscribe_from_other_levels(user, project)
 
-    find_or_initialize_subscription(user, project).
-      update(subscribed: !subscribed?(user, project))
+    find_or_initialize_subscription(user, project)
+      .update(subscribed: !subscribed?(user, project))
   end
 
   def subscribe(user, project = nil)
@@ -69,14 +69,14 @@ module Subscribable
   end
 
   def find_or_initialize_subscription(user, project)
-    subscriptions.
-      find_or_initialize_by(user_id: user.id, project_id: project.try(:id))
+    subscriptions
+      .find_or_initialize_by(user_id: user.id, project_id: project.try(:id))
   end
 
   def subscriptions_available(project)
     t = Subscription.arel_table
 
-    subscriptions.
-      where(t[:project_id].eq(nil).or(t[:project_id].eq(project.try(:id))))
+    subscriptions
+      .where(t[:project_id].eq(nil).or(t[:project_id].eq(project.try(:id))))
   end
 end
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index 85e7901dfee504ae08932ea50cd4e5462753596e..056c49e7162e879c08376cb287d5cee2822d411d 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -58,10 +58,10 @@ class Deployment < ActiveRecord::Base
   def update_merge_request_metrics!
     return unless environment.update_merge_request_metrics?
 
-    merge_requests = project.merge_requests.
-                     joins(:metrics).
-                     where(target_branch: self.ref, merge_request_metrics: { first_deployed_to_production_at: nil }).
-                     where("merge_request_metrics.merged_at <= ?", self.created_at)
+    merge_requests = project.merge_requests
+                     .joins(:metrics)
+                     .where(target_branch: self.ref, merge_request_metrics: { first_deployed_to_production_at: nil })
+                     .where("merge_request_metrics.merged_at <= ?", self.created_at)
 
     if previous_deployment
       merge_requests = merge_requests.where("merge_request_metrics.merged_at >= ?", previous_deployment.created_at)
@@ -76,17 +76,17 @@ class Deployment < ActiveRecord::Base
         merge_requests.map(&:id)
       end
 
-    MergeRequest::Metrics.
-      where(merge_request_id: merge_request_ids, first_deployed_to_production_at: nil).
-      update_all(first_deployed_to_production_at: self.created_at)
+    MergeRequest::Metrics
+      .where(merge_request_id: merge_request_ids, first_deployed_to_production_at: nil)
+      .update_all(first_deployed_to_production_at: self.created_at)
   end
 
   def previous_deployment
     @previous_deployment ||=
-      project.deployments.joins(:environment).
-      where(environments: { name: self.environment.name }, ref: self.ref).
-      where.not(id: self.id).
-      take
+      project.deployments.joins(:environment)
+      .where(environments: { name: self.environment.name }, ref: self.ref)
+      .where.not(id: self.id)
+      .take
   end
 
   def stop_action
@@ -114,6 +114,17 @@ class Deployment < ActiveRecord::Base
     project.monitoring_service.deployment_metrics(self)
   end
 
+  def has_additional_metrics?
+    project.prometheus_service.present?
+  end
+
+  def additional_metrics
+    return {} unless project.prometheus_service.present?
+
+    metrics = project.prometheus_service.additional_deployment_metrics(self)
+    metrics&.merge(deployment_time: created_at.to_i) || {}
+  end
+
   private
 
   def ref_path
diff --git a/app/models/environment.rb b/app/models/environment.rb
index d5b974b2d31ba69d376fac3b5d421d34f1618a53..eb24ff00ce39e179206b8346ff5fcb534d51109b 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -40,11 +40,12 @@ class Environment < ActiveRecord::Base
   scope :stopped, -> { with_state(:stopped) }
   scope :order_by_last_deployed_at, -> do
     max_deployment_id_sql =
-      Deployment.select(Deployment.arel_table[:id].maximum).
-      where(Deployment.arel_table[:environment_id].eq(arel_table[:id])).
-      to_sql
+      Deployment.select(Deployment.arel_table[:id].maximum)
+      .where(Deployment.arel_table[:environment_id].eq(arel_table[:id]))
+      .to_sql
     order(Gitlab::Database.nulls_first_order("(#{max_deployment_id_sql})", 'ASC'))
   end
+  scope :in_review_folder, -> { where(environment_type: "review") }
 
   state_machine :state, initial: :available do
     event :start do
@@ -157,6 +158,16 @@ class Environment < ActiveRecord::Base
     project.monitoring_service.environment_metrics(self) if has_metrics?
   end
 
+  def has_additional_metrics?
+    project.prometheus_service.present? && available? && last_deployment.present?
+  end
+
+  def additional_metrics
+    if has_additional_metrics?
+      project.prometheus_service.additional_environment_metrics(self)
+    end
+  end
+
   # An environment name is not necessarily suitable for use in URLs, DNS
   # or other third-party contexts, so provide a slugified version. A slug has
   # the following properties:
@@ -207,8 +218,7 @@ class Environment < ActiveRecord::Base
   end
 
   def etag_cache_key
-    Gitlab::Routing.url_helpers.namespace_project_environments_path(
-      project.namespace,
+    Gitlab::Routing.url_helpers.project_environments_path(
       project,
       format: :json)
   end
diff --git a/app/models/event.rb b/app/models/event.rb
index fad6ff03927f4780dab5e5f5ab4edcf57699a086..29bc141c5cdf7a0d4569e0a5f7849dc9c6343c14 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -376,9 +376,9 @@ class Event < ActiveRecord::Base
     # At this point it's possible for multiple threads/processes to try to
     # update the project. Only one query should actually perform the update,
     # hence we add the extra WHERE clause for last_activity_at.
-    Project.unscoped.where(id: project_id).
-      where('last_activity_at <= ?', RESET_PROJECT_ACTIVITY_INTERVAL.ago).
-      update_all(last_activity_at: created_at)
+    Project.unscoped.where(id: project_id)
+      .where('last_activity_at <= ?', RESET_PROJECT_ACTIVITY_INTERVAL.ago)
+      .update_all(last_activity_at: created_at)
   end
 
   def authored_by?(user)
@@ -392,7 +392,7 @@ class Event < ActiveRecord::Base
   end
 
   def set_last_repository_updated_at
-    Project.unscoped.where(id: project_id).
-      update_all(last_repository_updated_at: created_at)
+    Project.unscoped.where(id: project_id)
+      .update_all(last_repository_updated_at: created_at)
   end
 end
diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb
index e63f89a9f851abf369a345d5b15b500f9a1a6926..0bf18e529f029e5761290a711194bc04544fe4e4 100644
--- a/app/models/external_issue.rb
+++ b/app/models/external_issue.rb
@@ -38,11 +38,6 @@ class ExternalIssue
     @project.id
   end
 
-  # Pattern used to extract `JIRA-123` issue references from text
-  def self.reference_pattern
-    @reference_pattern ||= %r{(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)}
-  end
-
   def to_reference(_from_project = nil, full: nil)
     id
   end
diff --git a/app/models/forked_project_link.rb b/app/models/forked_project_link.rb
index 36cf7ad6a280584645d975ada0130c2059c1c11c..8d35864eff64296750fda4c6b63f261e4ff2711c 100644
--- a/app/models/forked_project_link.rb
+++ b/app/models/forked_project_link.rb
@@ -1,4 +1,4 @@
 class ForkedProjectLink < ActiveRecord::Base
-  belongs_to :forked_to_project, class_name: 'Project'
-  belongs_to :forked_from_project, class_name: 'Project'
+  belongs_to :forked_to_project, -> { where.not(pending_delete: true) }, class_name: 'Project'
+  belongs_to :forked_from_project, -> { where.not(pending_delete: true) }, class_name: 'Project'
 end
diff --git a/app/models/group.rb b/app/models/group.rb
index 5bb2cdc5eff793e3380049271bd06b109190dc7b..a6fdb30f84cee80d91f2df9bf2b833f0ad8635ee 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -206,8 +206,8 @@ class Group < Namespace
   end
 
   def refresh_members_authorized_projects
-    UserProjectAccessChangedService.new(user_ids_for_project_authorizations).
-      execute
+    UserProjectAccessChangedService.new(user_ids_for_project_authorizations)
+      .execute
   end
 
   def user_ids_for_project_authorizations
@@ -222,13 +222,19 @@ class Group < Namespace
     User.where(id: members_with_parents.select(:user_id))
   end
 
+  def users_with_descendants
+    members_with_descendants = GroupMember.non_request.where(source_id: descendants.pluck(:id).push(id))
+
+    User.where(id: members_with_descendants.select(:user_id))
+  end
+
   def max_member_access_for_user(user)
     return GroupMember::OWNER if user.admin?
 
-    members_with_parents.
-      where(user_id: user).
-      reorder(access_level: :desc).
-      first&.
+    members_with_parents
+      .where(user_id: user)
+      .reorder(access_level: :desc)
+      .first&.
       access_level || GroupMember::NO_ACCESS
   end
 
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 693cc21bb405063190fc9fd8f51a0fb5ebb72141..a97e88f76f6de693390a21cd6e56b9e977f1f6b7 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -9,6 +9,9 @@ class Issue < ActiveRecord::Base
   include Spammable
   include FasterCacheKeys
   include RelativePositioning
+  include IgnorableColumn
+
+  ignore_column :position
 
   DueDateStruct = Struct.new(:title, :name).freeze
   NoDueDate     = DueDateStruct.new('No Due Date', '0').freeze
@@ -44,7 +47,7 @@ class Issue < ActiveRecord::Base
 
   scope :created_after, -> (datetime) { where("created_at >= ?", datetime) }
 
-  scope :include_associations, -> { includes(:labels, project: :namespace) }
+  scope :preload_associations, -> { preload(:labels, project: :namespace) }
 
   after_save :expire_etag_cache
 
@@ -121,8 +124,8 @@ class Issue < ActiveRecord::Base
   end
 
   def self.order_by_position_and_priority
-    order_labels_priority.
-      reorder(Gitlab::Database.nulls_last_order('relative_position', 'ASC'),
+    order_labels_priority
+      .reorder(Gitlab::Database.nulls_last_order('relative_position', 'ASC'),
               Gitlab::Database.nulls_last_order('highest_priority', 'ASC'),
               "id DESC")
   end
@@ -292,11 +295,7 @@ class Issue < ActiveRecord::Base
   end
 
   def expire_etag_cache
-    key = Gitlab::Routing.url_helpers.realtime_changes_namespace_project_issue_path(
-      project.namespace,
-      project,
-      self
-    )
+    key = Gitlab::Routing.url_helpers.realtime_changes_project_issue_path(project, self)
     Gitlab::EtagCaching::Store.new.touch(key)
   end
 end
diff --git a/app/models/issue_collection.rb b/app/models/issue_collection.rb
index f0b7d9914c80167f9c5aa94cbcaa3dd9e9b019d7..49f011c113f58d96adcf5f3fd41104b5ae599e6f 100644
--- a/app/models/issue_collection.rb
+++ b/app/models/issue_collection.rb
@@ -17,9 +17,9 @@ class IssueCollection
 
     # Given all the issue projects we get a list of projects that the current
     # user has at least reporter access to.
-    projects_with_reporter_access = user.
-      projects_with_reporter_access_limited_to(project_ids).
-      pluck(:id)
+    projects_with_reporter_access = user
+      .projects_with_reporter_access_limited_to(project_ids)
+      .pluck(:id)
 
     collection.select do |issue|
       if projects_with_reporter_access.include?(issue.project_id)
diff --git a/app/models/label.rb b/app/models/label.rb
index 955d6b4079b633f94bd6f7394846e72c0897fb86..ed6a8411da9dd2de3f57bd14a2eee9e3b7822daf 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -46,9 +46,9 @@ class Label < ActiveRecord::Base
     labels = Label.arel_table
     priorities = LabelPriority.arel_table
 
-    label_priorities = labels.join(priorities, Arel::Nodes::OuterJoin).
-                              on(labels[:id].eq(priorities[:label_id]).and(priorities[:project_id].eq(project.id))).
-                              join_sources
+    label_priorities = labels.join(priorities, Arel::Nodes::OuterJoin)
+                              .on(labels[:id].eq(priorities[:label_id]).and(priorities[:project_id].eq(project.id)))
+                              .join_sources
 
     joins(label_priorities).where(priorities[:priority].eq(nil))
   end
@@ -57,9 +57,9 @@ class Label < ActiveRecord::Base
     labels = Label.arel_table
     priorities = LabelPriority.arel_table
 
-    label_priorities = labels.join(priorities, Arel::Nodes::OuterJoin).
-                              on(labels[:id].eq(priorities[:label_id])).
-                              join_sources
+    label_priorities = labels.join(priorities, Arel::Nodes::OuterJoin)
+                              .on(labels[:id].eq(priorities[:label_id]))
+                              .join_sources
 
     joins(label_priorities)
   end
diff --git a/app/models/legacy_diff_note.rb b/app/models/legacy_diff_note.rb
index 7126de2d488ead32b6dac5336c30c4afeac9ee52..2d5909ab25eac5abd871e261e4b5ef447d2f9240 100644
--- a/app/models/legacy_diff_note.rb
+++ b/app/models/legacy_diff_note.rb
@@ -42,7 +42,7 @@ class LegacyDiffNote < Note
   end
 
   def for_line?(line)
-    !line.meta? && diff_file.line_code(line) == self.line_code
+    line.discussable? && diff_file.line_code(line) == self.line_code
   end
 
   def original_line_code
diff --git a/app/models/member.rb b/app/models/member.rb
index 788a32dd8e3b14ba242ca6c2c2e3023ba48d9ba4..dc9247bc9a0da79e93013d09fa4e9f1377cdd708 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -99,9 +99,9 @@ class Member < ActiveRecord::Base
       users = User.arel_table
       members = Member.arel_table
 
-      member_users = members.join(users, Arel::Nodes::OuterJoin).
-                             on(members[:user_id].eq(users[:id])).
-                             join_sources
+      member_users = members.join(users, Arel::Nodes::OuterJoin)
+                             .on(members[:user_id].eq(users[:id]))
+                             .join_sources
 
       joins(member_users)
     end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index dd155252ad5f8049853f92754e72919436d366db..808212c780cbc9b784c6278a67a47c6ede721e14 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -4,6 +4,9 @@ class MergeRequest < ActiveRecord::Base
   include Noteable
   include Referable
   include Sortable
+  include IgnorableColumn
+
+  ignore_column :position
 
   belongs_to :target_project, class_name: "Project"
   belongs_to :source_project, class_name: "Project"
@@ -194,11 +197,19 @@ class MergeRequest < ActiveRecord::Base
     }
   end
 
-  # This method is needed for compatibility with issues to not mess view and other code
+  # These method are needed for compatibility with issues to not mess view and other code
   def assignees
     Array(assignee)
   end
 
+  def assignee_ids
+    Array(assignee_id)
+  end
+
+  def assignee_ids=(ids)
+    write_attribute(:assignee_id, ids.last)
+  end
+
   def assignee_or_author?(user)
     author_id == user.id || assignee_id == user.id
   end
@@ -574,8 +585,8 @@ class MergeRequest < ActiveRecord::Base
       messages = [title, description]
       messages.concat(commits.map(&:safe_message)) if merge_request_diff
 
-      Gitlab::ClosingIssueExtractor.new(project, current_user).
-        closed_by_message(messages.join("\n"))
+      Gitlab::ClosingIssueExtractor.new(project, current_user)
+        .closed_by_message(messages.join("\n"))
     else
       []
     end
@@ -768,6 +779,7 @@ class MergeRequest < ActiveRecord::Base
       "refs/heads/#{source_branch}",
       ref_path
     )
+    update_column(:ref_fetched, true)
   end
 
   def ref_path
@@ -775,7 +787,13 @@ class MergeRequest < ActiveRecord::Base
   end
 
   def ref_fetched?
-    project.repository.ref_exists?(ref_path)
+    super ||
+      begin
+        computed_value = project.repository.ref_exists?(ref_path)
+        update_column(:ref_fetched, true) if computed_value
+
+        computed_value
+      end
   end
 
   def ensure_ref_fetched
@@ -889,7 +907,7 @@ class MergeRequest < ActiveRecord::Base
     !has_commits?
   end
 
-  def mergeable_with_slash_command?(current_user, autocomplete_precheck: false, last_diff_sha: nil)
+  def mergeable_with_quick_action?(current_user, autocomplete_precheck: false, last_diff_sha: nil)
     return false unless can_be_merged_by?(current_user)
 
     return true if autocomplete_precheck
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 99dd21301880d71465d2b805d495b54a21e1105e..f1ee4d3f7a91d7781005e0c6b997d5b00e546e21 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -10,6 +10,7 @@ class MergeRequestDiff < ActiveRecord::Base
   VALID_CLASSES = [Hash, Rugged::Patch, Rugged::Diff::Delta].freeze
 
   belongs_to :merge_request
+  has_many :merge_request_diff_files, -> { order(:merge_request_diff_id, :relative_order) }
 
   serialize :st_commits # rubocop:disable Cop/ActiverecordSerialize
   serialize :st_diffs # rubocop:disable Cop/ActiverecordSerialize
@@ -91,7 +92,7 @@ class MergeRequestDiff < ActiveRecord::Base
           head_commit_sha).diffs(options)
     else
       @raw_diffs ||= {}
-      @raw_diffs[options] ||= load_diffs(st_diffs, options)
+      @raw_diffs[options] ||= load_diffs(options)
     end
   end
 
@@ -253,24 +254,44 @@ class MergeRequestDiff < ActiveRecord::Base
     update_columns_serialized(new_attributes)
   end
 
-  def dump_diffs(diffs)
-    if diffs.respond_to?(:map)
-      diffs.map(&:to_hash)
+  def create_merge_request_diff_files(diffs)
+    rows = diffs.map.with_index do |diff, index|
+      diff.to_hash.merge(
+        merge_request_diff_id: self.id,
+        relative_order: index
+      )
     end
+
+    Gitlab::Database.bulk_insert('merge_request_diff_files', rows)
   end
 
-  def load_diffs(raw, options)
-    if valid_raw_diff?(raw)
-      if paths = options[:paths]
-        raw = raw.select do |diff|
-          paths.include?(diff[:old_path]) || paths.include?(diff[:new_path])
-        end
-      end
+  def load_diffs(options)
+    return Gitlab::Git::DiffCollection.new([]) unless diffs_from_database
 
-      Gitlab::Git::DiffCollection.new(raw, options)
-    else
-      Gitlab::Git::DiffCollection.new([])
+    raw = diffs_from_database
+
+    if paths = options[:paths]
+      raw = raw.select do |diff|
+        paths.include?(diff[:old_path]) || paths.include?(diff[:new_path])
+      end
     end
+
+    Gitlab::Git::DiffCollection.new(raw, options)
+  end
+
+  def diffs_from_database
+    return @diffs_from_database if defined?(@diffs_from_database)
+
+    @diffs_from_database =
+      if st_diffs.present?
+        if valid_raw_diff?(st_diffs)
+          st_diffs
+        end
+      elsif merge_request_diff_files.present?
+        merge_request_diff_files
+          .as_json(only: Gitlab::Git::Diff::SERIALIZE_KEYS)
+          .map(&:with_indifferent_access)
+      end
   end
 
   # Load diffs between branches related to current merge request diff from repo
@@ -285,11 +306,10 @@ class MergeRequestDiff < ActiveRecord::Base
       new_attributes[:real_size] = diff_collection.real_size
 
       if diff_collection.any?
-        new_diffs = dump_diffs(diff_collection)
         new_attributes[:state] = :collected
-      end
 
-      new_attributes[:st_diffs] = new_diffs || []
+        create_merge_request_diff_files(diff_collection)
+      end
 
       # Set our state to 'overflow' to make the #empty? and #collected?
       # methods (generated by StateMachine) return false.
diff --git a/app/models/merge_request_diff_file.rb b/app/models/merge_request_diff_file.rb
new file mode 100644
index 0000000000000000000000000000000000000000..598ebd4d829ed718e237c72678517ff0c6928d85
--- /dev/null
+++ b/app/models/merge_request_diff_file.rb
@@ -0,0 +1,11 @@
+class MergeRequestDiffFile < ActiveRecord::Base
+  include Gitlab::EncodingHelper
+
+  belongs_to :merge_request_diff
+
+  def utf8_diff
+    return '' if diff.blank?
+
+    encode_utf8(diff) if diff.respond_to?(:encoding)
+  end
+end
diff --git a/app/models/merge_requests_closing_issues.rb b/app/models/merge_requests_closing_issues.rb
index daafb137be4c2be3bb7eb360abad55d025ac10df..7f7c114803dd55154a33ba209a406b504702a4be 100644
--- a/app/models/merge_requests_closing_issues.rb
+++ b/app/models/merge_requests_closing_issues.rb
@@ -7,9 +7,9 @@ class MergeRequestsClosingIssues < ActiveRecord::Base
 
   class << self
     def count_for_collection(ids)
-      group(:issue_id).
-        where(issue_id: ids).
-        pluck('issue_id', 'COUNT(*) as count')
+      group(:issue_id)
+        .where(issue_id: ids)
+        .pluck('issue_id', 'COUNT(*) as count')
     end
   end
 end
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index b04bed4c0147679a04f948771886c3a95047a7f3..d2e2749f70dfce1696b5f9b01e7f752cf1632c49 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -98,11 +98,11 @@ class Milestone < ActiveRecord::Base
     if Gitlab::Database.postgresql?
       rel.order(:project_id, :due_date).select('DISTINCT ON (project_id) id')
     else
-      rel.
-        group(:project_id).
-        having('due_date = MIN(due_date)').
-        pluck(:id, :project_id, :due_date).
-        map(&:first)
+      rel
+        .group(:project_id)
+        .having('due_date = MIN(due_date)')
+        .pluck(:id, :project_id, :due_date)
+        .map(&:first)
     end
   end
 
@@ -164,38 +164,6 @@ class Milestone < ActiveRecord::Base
     write_attribute(:title, sanitize_title(value)) if value.present?
   end
 
-  # Sorts the issues for the given IDs.
-  #
-  # This method runs a single SQL query using a CASE statement to update the
-  # position of all issues in the current milestone (scoped to the list of IDs).
-  #
-  # Given the ids [10, 20, 30] this method produces a SQL query something like
-  # the following:
-  #
-  #     UPDATE issues
-  #     SET position = CASE
-  #       WHEN id = 10 THEN 1
-  #       WHEN id = 20 THEN 2
-  #       WHEN id = 30 THEN 3
-  #       ELSE position
-  #     END
-  #     WHERE id IN (10, 20, 30);
-  #
-  # This method expects that the IDs given in `ids` are already Fixnums.
-  def sort_issues(ids)
-    pairs = []
-
-    ids.each_with_index do |id, index|
-      pairs << id
-      pairs << index + 1
-    end
-
-    conditions = 'WHEN id = ? THEN ? ' * ids.length
-
-    issues.where(id: ids).
-      update_all(["position = CASE #{conditions} ELSE position END", *pairs])
-  end
-
   private
 
   def milestone_format_reference(format = :iid)
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index b48d73dcae7e90e6a05ef9f25a2a958909c3927e..672eab94c07ff54ffa4bf57c93dac8c6c82473df 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -1,5 +1,5 @@
 class Namespace < ActiveRecord::Base
-  acts_as_paranoid
+  acts_as_paranoid without_default_scope: true
 
   include CacheMarkdownField
   include Sortable
@@ -181,16 +181,16 @@ class Namespace < ActiveRecord::Base
   def ancestors
     return self.class.none unless parent_id
 
-    Gitlab::GroupHierarchy.
-      new(self.class.where(id: parent_id)).
-      base_and_ancestors
+    Gitlab::GroupHierarchy
+      .new(self.class.where(id: parent_id))
+      .base_and_ancestors
   end
 
   # Returns all the descendants of the current namespace.
   def descendants
-    Gitlab::GroupHierarchy.
-      new(self.class.where(parent_id: id)).
-      base_and_descendants
+    Gitlab::GroupHierarchy
+      .new(self.class.where(parent_id: id))
+      .base_and_descendants
   end
 
   def user_ids_for_project_authorizations
@@ -219,6 +219,12 @@ class Namespace < ActiveRecord::Base
     parent.present?
   end
 
+  def soft_delete_without_removing_associations
+    # We can't use paranoia's `#destroy` since this will hard-delete projects.
+    # Project uses `pending_delete` instead of the acts_as_paranoia gem.
+    self.deleted_at = Time.now
+  end
+
   private
 
   def repository_storage_paths
@@ -253,10 +259,10 @@ class Namespace < ActiveRecord::Base
   end
 
   def refresh_access_of_projects_invited_groups
-    Group.
-      joins(project_group_links: :project).
-      where(projects: { namespace_id: id }).
-      find_each(&:refresh_members_authorized_projects)
+    Group
+      .joins(project_group_links: :project)
+      .where(projects: { namespace_id: id })
+      .find_each(&:refresh_members_authorized_projects)
   end
 
   def remove_exports!
diff --git a/app/models/network/graph.rb b/app/models/network/graph.rb
index 59737bb60857396a006811ef58badde58289a366..2bc00a082df33301fa261e70df8d7718cf92f1fe 100644
--- a/app/models/network/graph.rb
+++ b/app/models/network/graph.rb
@@ -113,7 +113,7 @@ module Network
 
       opts[:ref] = @commit.id if @filter_ref
 
-      @repo.find_commits(opts)
+      Gitlab::Git::Commit.find_all(@repo.raw_repository, opts)
     end
 
     def commits_sort_by_ref
diff --git a/app/models/note.rb b/app/models/note.rb
index 244bf169c290c2c47a6fd6cb55c6cc397e2d4d1c..dfd435bcdf6c6883d31325e2d3b0ae4e22b541bc 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -32,7 +32,7 @@ class Note < ActiveRecord::Base
   # Banzai::ObjectRenderer
   attr_accessor :user_visible_reference_count
 
-  # Attribute used to store the attributes that have ben changed by slash commands.
+  # Attribute used to store the attributes that have ben changed by quick actions.
   attr_accessor :commands_changes
 
   default_value_for :system, false
@@ -137,9 +137,9 @@ class Note < ActiveRecord::Base
     end
 
     def count_for_collection(ids, type)
-      user.select('noteable_id', 'COUNT(*) as count').
-        group(:noteable_id).
-        where(noteable_type: type, noteable_id: ids)
+      user.select('noteable_id', 'COUNT(*) as count')
+        .group(:noteable_id)
+        .where(noteable_type: type, noteable_id: ids)
     end
   end
 
@@ -330,8 +330,7 @@ class Note < ActiveRecord::Base
   def expire_etag_cache
     return unless for_issue?
 
-    key = Gitlab::Routing.url_helpers.namespace_project_noteable_notes_path(
-      noteable.project.namespace,
+    key = Gitlab::Routing.url_helpers.project_noteable_notes_path(
       noteable.project,
       target_type: noteable_type.underscore,
       target_id: noteable.id
diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb
index b0df7aeb323a586036a7236a32d0c4de4a94eee6..81844b1e2ca4612224076b5c201d9f615feeac88 100644
--- a/app/models/notification_setting.rb
+++ b/app/models/notification_setting.rb
@@ -19,7 +19,7 @@ class NotificationSetting < ActiveRecord::Base
   # pending delete).
   #
   scope :for_projects, -> do
-    includes(:project).references(:projects).where(source_type: 'Project').where.not(projects: { id: nil })
+    includes(:project).references(:projects).where(source_type: 'Project').where.not(projects: { id: nil, pending_delete: true })
   end
 
   EMAIL_EVENTS = [
diff --git a/app/models/project.rb b/app/models/project.rb
index 4c394646787c50336d6f062944e78d3650cc1bd8..3a5a01db518a91df8b1012c2ba3b1556ce0779ee 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -186,6 +186,11 @@ class Project < ActiveRecord::Base
   # Validations
   validates :creator, presence: true, on: :create
   validates :description, length: { maximum: 2000 }, allow_blank: true
+  validates :ci_config_path,
+    format: { without: /\.{2}/,
+              message: 'cannot include directory traversal.' },
+    length: { maximum: 255 },
+    allow_blank: true
   validates :name,
     presence: true,
     length: { maximum: 255 },
@@ -222,9 +227,8 @@ class Project < ActiveRecord::Base
   has_many :uploads, as: :model, dependent: :destroy
 
   # Scopes
-  default_scope { where(pending_delete: false) }
-
-  scope :with_deleted, -> { unscope(where: :pending_delete) }
+  scope :pending_delete, -> { where(pending_delete: true) }
+  scope :without_deleted, -> { where(pending_delete: false) }
 
   scope :sorted_by_activity, -> { reorder(last_activity_at: :desc) }
   scope :sorted_by_stars, -> { reorder('projects.star_count DESC') }
@@ -244,8 +248,8 @@ class Project < ActiveRecord::Base
   scope :inside_path, ->(path) do
     # We need routes alias rs for JOIN so it does not conflict with
     # includes(:route) which we use in ProjectsFinder.
-    joins("INNER JOIN routes rs ON rs.source_id = projects.id AND rs.source_type = 'Project'").
-      where('rs.path LIKE ?', "#{sanitize_sql_like(path)}/%")
+    joins("INNER JOIN routes rs ON rs.source_id = projects.id AND rs.source_type = 'Project'")
+      .where('rs.path LIKE ?', "#{sanitize_sql_like(path)}/%")
   end
 
   # "enabled" here means "not disabled". It includes private features!
@@ -266,20 +270,49 @@ class Project < ActiveRecord::Base
 
   enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 }
 
+  # Returns a collection of projects that is either public or visible to the
+  # logged in user.
+  def self.public_or_visible_to_user(user = nil)
+    if user
+      authorized = user
+        .project_authorizations
+        .select(1)
+        .where('project_authorizations.project_id = projects.id')
+
+      levels = Gitlab::VisibilityLevel.levels_for_user(user)
+
+      where('EXISTS (?) OR projects.visibility_level IN (?)', authorized, levels)
+    else
+      public_to_user
+    end
+  end
+
   # project features may be "disabled", "internal" or "enabled". If "internal",
   # they are only available to team members. This scope returns projects where
   # the feature is either enabled, or internal with permission for the user.
+  #
+  # This method uses an optimised version of `with_feature_access_level` for
+  # logged in users to more efficiently get private projects with the given
+  # feature.
   def self.with_feature_available_for_user(feature, user)
-    return with_feature_enabled(feature) if user.try(:admin?)
+    visible = [nil, ProjectFeature::ENABLED]
 
-    unconditional = with_feature_access_level(feature, [nil, ProjectFeature::ENABLED])
-    return unconditional if user.nil?
+    if user&.admin?
+      with_feature_enabled(feature)
+    elsif user
+      column = ProjectFeature.quoted_access_level_column(feature)
 
-    conditional = with_feature_access_level(feature, ProjectFeature::PRIVATE)
-    authorized = user.authorized_projects.merge(conditional.reorder(nil))
+      authorized = user.project_authorizations.select(1)
+        .where('project_authorizations.project_id = projects.id')
 
-    union = Gitlab::SQL::Union.new([unconditional.select(:id), authorized.select(:id)])
-    where(arel_table[:id].in(Arel::Nodes::SqlLiteral.new(union.to_sql)))
+      with_project_feature
+        .where("#{column} IN (?) OR (#{column} = ? AND EXISTS (?))",
+              visible,
+              ProjectFeature::PRIVATE,
+              authorized)
+    else
+      with_feature_access_level(feature, visible)
+    end
   end
 
   scope :active, -> { joins(:issues, :notes, :merge_requests).order('issues.created_at, notes.created_at, merge_requests.created_at DESC') }
@@ -321,7 +354,19 @@ class Project < ActiveRecord::Base
       project.run_after_commit { add_import_job }
     end
 
-    after_transition started: :finished, do: :reset_cache_and_import_attrs
+    after_transition started: :finished do |project, _|
+      project.reset_cache_and_import_attrs
+
+      if Gitlab::ImportSources.importer_names.include?(project.import_type) && project.repo_exists?
+        project.run_after_commit do
+          begin
+            Projects::HousekeepingService.new(project).execute
+          rescue Projects::HousekeepingService::LeaseTaken => e
+            Rails.logger.info("Could not perform housekeeping for project #{project.path_with_namespace} (#{project.id}): #{e}")
+          end
+        end
+      end
+    end
   end
 
   class << self
@@ -340,14 +385,14 @@ class Project < ActiveRecord::Base
       # unscoping unnecessary conditions that'll be applied
       # when executing `where("projects.id IN (#{union.to_sql})")`
       projects = unscoped.select(:id).where(
-        ptable[:path].matches(pattern).
-          or(ptable[:name].matches(pattern)).
-          or(ptable[:description].matches(pattern))
+        ptable[:path].matches(pattern)
+          .or(ptable[:name].matches(pattern))
+          .or(ptable[:description].matches(pattern))
       )
 
-      namespaces = unscoped.select(:id).
-        joins(:namespace).
-        where(ntable[:name].matches(pattern))
+      namespaces = unscoped.select(:id)
+        .joins(:namespace)
+        .where(ntable[:name].matches(pattern))
 
       union = Gitlab::SQL::Union.new([projects, namespaces])
 
@@ -388,8 +433,8 @@ class Project < ActiveRecord::Base
     end
 
     def trending
-      joins('INNER JOIN trending_projects ON projects.id = trending_projects.project_id').
-        reorder('trending_projects.id ASC')
+      joins('INNER JOIN trending_projects ON projects.id = trending_projects.project_id')
+        .reorder('trending_projects.id ASC')
     end
 
     def cached_count
@@ -478,11 +523,12 @@ class Project < ActiveRecord::Base
       ProjectCacheWorker.perform_async(self.id)
     end
 
-    remove_import_data
+    import_data&.destroy
   end
 
-  def remove_import_data
-    import_data&.destroy
+  def ci_config_path=(value)
+    # Strip all leading slashes so that //foo -> foo
+    super(value&.sub(%r{\A/+}, '')&.delete("\0"))
   end
 
   def import_url=(value)
@@ -639,7 +685,7 @@ class Project < ActiveRecord::Base
   end
 
   def web_url
-    Gitlab::Routing.url_helpers.namespace_project_url(self.namespace, self)
+    Gitlab::Routing.url_helpers.project_url(self)
   end
 
   def new_issue_address(author)
@@ -660,7 +706,7 @@ class Project < ActiveRecord::Base
   end
 
   def last_activity_date
-    last_activity_at || updated_at
+    last_repository_updated_at || last_activity_at || updated_at
   end
 
   def project_id
@@ -691,8 +737,8 @@ class Project < ActiveRecord::Base
     end
   end
 
-  def issue_reference_pattern
-    issues_tracker.reference_pattern
+  def external_issue_reference_pattern
+    external_issue_tracker.class.reference_pattern
   end
 
   def default_issues_tracker?
@@ -779,7 +825,7 @@ class Project < ActiveRecord::Base
   end
 
   def ci_service
-    @ci_service ||= ci_services.reorder(nil).find_by(active: true)
+    @ci_service ||= ci_services.find_by(active: true)
   end
 
   def deployment_services
@@ -787,7 +833,7 @@ class Project < ActiveRecord::Base
   end
 
   def deployment_service
-    @deployment_service ||= deployment_services.reorder(nil).find_by(active: true)
+    @deployment_service ||= deployment_services.find_by(active: true)
   end
 
   def monitoring_services
@@ -795,7 +841,7 @@ class Project < ActiveRecord::Base
   end
 
   def monitoring_service
-    @monitoring_service ||= monitoring_services.reorder(nil).find_by(active: true)
+    @monitoring_service ||= monitoring_services.find_by(active: true)
   end
 
   def jira_tracker?
@@ -815,7 +861,7 @@ class Project < ActiveRecord::Base
   def avatar_url(**args)
     # We use avatar_path instead of overriding avatar_url because of carrierwave.
     # See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11001/diffs#note_28659864
-    avatar_path(args) || (Gitlab::Routing.url_helpers.namespace_project_avatar_url(namespace, self) if avatar_in_git)
+    avatar_path(args) || (Gitlab::Routing.url_helpers.project_avatar_url(self) if avatar_in_git)
   end
 
   # For compatibility with old code
@@ -927,6 +973,7 @@ class Project < ActiveRecord::Base
       begin
         gitlab_shell.mv_repository(repository_storage_path, "#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki")
         send_move_instructions(old_path_with_namespace)
+        expires_full_path_cache
 
         @old_path_with_namespace = old_path_with_namespace
 
@@ -978,7 +1025,8 @@ class Project < ActiveRecord::Base
       namespace: namespace.name,
       visibility_level: visibility_level,
       path_with_namespace: path_with_namespace,
-      default_branch: default_branch
+      default_branch: default_branch,
+      ci_config_path: ci_config_path
     }
 
     # Backward compatibility
@@ -1037,19 +1085,23 @@ class Project < ActiveRecord::Base
     merge_requests.where(source_project_id: self.id)
   end
 
-  def create_repository
+  def create_repository(force: false)
     # Forked import is handled asynchronously
-    unless forked?
-      if gitlab_shell.add_repository(repository_storage_path, path_with_namespace)
-        repository.after_create
-        true
-      else
-        errors.add(:base, 'Failed to create repository via gitlab-shell')
-        false
-      end
+    return if forked? && !force
+
+    if gitlab_shell.add_repository(repository_storage_path, path_with_namespace)
+      repository.after_create
+      true
+    else
+      errors.add(:base, 'Failed to create repository via gitlab-shell')
+      false
     end
   end
 
+  def ensure_repository
+    create_repository(force: true) unless repository_exists?
+  end
+
   def repository_exists?
     !!repository.exists?
   end
@@ -1412,7 +1464,7 @@ class Project < ActiveRecord::Base
   def pending_delete_twin
     return false unless path
 
-    Project.unscoped.where(pending_delete: true).find_by_full_path(path_with_namespace)
+    Project.pending_delete.find_by_full_path(path_with_namespace)
   end
 
   ##
diff --git a/app/models/project_authorization.rb b/app/models/project_authorization.rb
index def0967525378578a30d2013ced9c5a0a8514761..73302207e6bbe0bdb963894fd3560d35a3fb3ce9 100644
--- a/app/models/project_authorization.rb
+++ b/app/models/project_authorization.rb
@@ -7,9 +7,9 @@ class ProjectAuthorization < ActiveRecord::Base
   validates :user, uniqueness: { scope: [:project, :access_level] }, presence: true
 
   def self.select_from_union(union)
-    select(['project_id', 'MAX(access_level) AS access_level']).
-      from("(#{union.to_sql}) #{ProjectAuthorization.table_name}").
-      group(:project_id)
+    select(['project_id', 'MAX(access_level) AS access_level'])
+      .from("(#{union.to_sql}) #{ProjectAuthorization.table_name}")
+      .group(:project_id)
   end
 
   def self.insert_authorizations(rows, per_batch = 1000)
diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb
index e3ef4919b2877ec7caa094a836289c50aa6742b2..c8fabb16dc19f974ecffd03864b9060d2446846a 100644
--- a/app/models/project_feature.rb
+++ b/app/models/project_feature.rb
@@ -27,6 +27,13 @@ class ProjectFeature < ActiveRecord::Base
 
       "#{feature}_access_level".to_sym
     end
+
+    def quoted_access_level_column(feature)
+      attribute = connection.quote_column_name(access_level_attribute(feature))
+      table = connection.quote_table_name(table_name)
+
+      "#{table}.#{attribute}"
+    end
   end
 
   # Default scopes force us to unscope here since a service may need to check
@@ -44,8 +51,11 @@ class ProjectFeature < ActiveRecord::Base
   default_value_for :repository_access_level,     value: ENABLED, allows_nil: false
 
   def feature_available?(feature, user)
-    access_level = public_send(ProjectFeature.access_level_attribute(feature))
-    get_permission(user, access_level)
+    get_permission(user, access_level(feature))
+  end
+
+  def access_level(feature)
+    public_send(ProjectFeature.access_level_attribute(feature))
   end
 
   def builds_enabled?
@@ -83,7 +93,7 @@ class ProjectFeature < ActiveRecord::Base
     when DISABLED
       false
     when PRIVATE
-      user && (project.team.member?(user) || user.admin?)
+      user && (project.team.member?(user) || user.full_private_access?)
     when ENABLED
       true
     else
diff --git a/app/models/project_services/chat_message/pipeline_message.rb b/app/models/project_services/chat_message/pipeline_message.rb
index 3edc395033cf234214725d22ad7b8eff680b1807..d63d4ec2b127d316abc2b1a44989bb282c0cea7f 100644
--- a/app/models/project_services/chat_message/pipeline_message.rb
+++ b/app/models/project_services/chat_message/pipeline_message.rb
@@ -70,7 +70,7 @@ module ChatMessage
     end
 
     def branch_link
-      "`[#{ref}](#{branch_url})`"
+      "[#{ref}](#{branch_url})"
     end
 
     def project_link
diff --git a/app/models/project_services/chat_message/push_message.rb b/app/models/project_services/chat_message/push_message.rb
index 04a59d559cae0f32a61d82134e23f915378b284b..c52dd6ef8ef451595dd26eda7f536bb79db76749 100644
--- a/app/models/project_services/chat_message/push_message.rb
+++ b/app/models/project_services/chat_message/push_message.rb
@@ -61,7 +61,7 @@ module ChatMessage
     end
 
     def removed_branch_message
-      "#{user_name} removed #{ref_type} `#{ref}` from #{project_link}"
+      "#{user_name} removed #{ref_type} #{ref} from #{project_link}"
     end
 
     def push_message
@@ -102,7 +102,7 @@ module ChatMessage
     end
 
     def branch_link
-      "`[#{ref}](#{branch_url})`"
+      "[#{ref}](#{branch_url})"
     end
 
     def project_link
diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb
index ad4eb9536e1157ba3a0141e07b057a71e7dcca44..5e31f393bbeecff1ebe58b269086f2d4a414a6f3 100644
--- a/app/models/project_services/gitlab_issue_tracker_service.rb
+++ b/app/models/project_services/gitlab_issue_tracker_service.rb
@@ -12,26 +12,26 @@ class GitlabIssueTrackerService < IssueTrackerService
   end
 
   def project_url
-    namespace_project_issues_url(project.namespace, project)
+    project_issues_url(project)
   end
 
   def new_issue_url
-    new_namespace_project_issue_url(namespace_id: project.namespace, project_id: project)
+    new_project_issue_url(project)
   end
 
   def issue_url(iid)
-    namespace_project_issue_url(namespace_id: project.namespace, project_id: project, id: iid)
+    project_issue_url(project, id: iid)
   end
 
   def project_path
-    namespace_project_issues_path(project.namespace, project)
+    project_issues_path(project)
   end
 
   def new_issue_path
-    new_namespace_project_issue_path(namespace_id: project.namespace, project_id: project)
+    new_project_issue_path(project)
   end
 
   def issue_path(iid)
-    namespace_project_issue_path(namespace_id: project.namespace, project_id: project, id: iid)
+    project_issue_path(project, id: iid)
   end
 end
diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb
index ff138b9066dced98a1a8884c9d82576a8d51f14f..1fa4cd4db301870415d1568ddbce232e031837f7 100644
--- a/app/models/project_services/issue_tracker_service.rb
+++ b/app/models/project_services/issue_tracker_service.rb
@@ -5,7 +5,10 @@ class IssueTrackerService < Service
 
   # Pattern used to extract links from comments
   # Override this method on services that uses different patterns
-  def reference_pattern
+  # This pattern does not support cross-project references
+  # The other code assumes that this pattern is a superset of all
+  # overriden patterns. See ReferenceRegexes::EXTERNAL_PATTERN
+  def self.reference_pattern
     @reference_pattern ||= %r{(\b[A-Z][A-Z0-9_]+-|#{Issue.reference_prefix})(?<issue>\d+)}
   end
 
@@ -18,7 +21,7 @@ class IssueTrackerService < Service
   end
 
   def project_path
-    project_url
+    read_attribute(:project_url)
   end
 
   def new_issue_path
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 2450fb43212e91a669afbdc1f7a91f108d9e4551..8af642b44aa545db7a99100f841decb46a88dfea 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -18,7 +18,7 @@ class JiraService < IssueTrackerService
   end
 
   # {PROJECT-KEY}-{NUMBER} Examples: JIRA-1, PROJECT-1
-  def reference_pattern
+  def self.reference_pattern
     @reference_pattern ||= %r{(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)}
   end
 
@@ -152,8 +152,8 @@ class JiraService < IssueTrackerService
         url: resource_url(user_path(author))
       },
       project: {
-        name: self.project.path_with_namespace,
-        url: resource_url(namespace_project_path(project.namespace, self.project))
+        name: project.path_with_namespace,
+        url: resource_url(namespace_project_path(project.namespace, project)) # rubocop:disable Cop/ProjectPathHelper
       },
       entity: {
         name: noteable_type.humanize.downcase,
diff --git a/app/models/project_services/mattermost_slash_commands_service.rb b/app/models/project_services/mattermost_slash_commands_service.rb
index 56f42d63b2d0c765c388d747406b4eaf7605f230..4d2037286a2e15f443a7e6851065e4a478549b22 100644
--- a/app/models/project_services/mattermost_slash_commands_service.rb
+++ b/app/models/project_services/mattermost_slash_commands_service.rb
@@ -1,4 +1,4 @@
-class MattermostSlashCommandsService < ChatSlashCommandsService
+class MattermostSlashCommandsService < SlashCommandsService
   include TriggersHelper
 
   prop_accessor :token
@@ -20,8 +20,8 @@ class MattermostSlashCommandsService < ChatSlashCommandsService
   end
 
   def configure(user, params)
-    token = Mattermost::Command.new(user).
-      create(command(params))
+    token = Mattermost::Command.new(user)
+      .create(command(params))
 
     update(active: true, token: token) if token
   rescue Mattermost::Error => e
diff --git a/app/models/project_services/prometheus_service.rb b/app/models/project_services/prometheus_service.rb
index 110b8bc209be6a4d9ee1b261802a401198525138..217f753f05f26a9b52b8a23a91ef714561e6afb7 100644
--- a/app/models/project_services/prometheus_service.rb
+++ b/app/models/project_services/prometheus_service.rb
@@ -28,17 +28,6 @@ class PrometheusService < MonitoringService
     'Prometheus monitoring'
   end
 
-  def help
-    <<-MD.strip_heredoc
-      Retrieves the Kubernetes node metrics `container_cpu_usage_seconds_total`
-      and `container_memory_usage_bytes` from the configured Prometheus server.
-
-      If you are not using [Auto-Deploy](https://docs.gitlab.com/ee/ci/autodeploy/index.html)
-      or have set up your own Prometheus server, an `environment` label is required on each metric to
-      [identify the Environment](https://docs.gitlab.com/ce/user/project/integrations/prometheus.html#metrics-and-labels).
-    MD
-  end
-
   def self.to_param
     'prometheus'
   end
@@ -50,6 +39,7 @@ class PrometheusService < MonitoringService
         name: 'api_url',
         title: 'API URL',
         placeholder: 'Prometheus API Base URL, like http://prometheus.example.com/',
+        help: 'By default, Prometheus listens on ‘http://localhost:9090’. It’s not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server.',
         required: true
       }
     ]
@@ -65,23 +55,34 @@ class PrometheusService < MonitoringService
   end
 
   def environment_metrics(environment)
-    with_reactive_cache(Gitlab::Prometheus::Queries::EnvironmentQuery.name, environment.id, &:itself)
+    with_reactive_cache(Gitlab::Prometheus::Queries::EnvironmentQuery.name, environment.id, &method(:rename_data_to_metrics))
   end
 
   def deployment_metrics(deployment)
-    metrics = with_reactive_cache(Gitlab::Prometheus::Queries::DeploymentQuery.name, deployment.id, &:itself)
-    metrics&.merge(deployment_time: created_at.to_i) || {}
+    metrics = with_reactive_cache(Gitlab::Prometheus::Queries::DeploymentQuery.name, deployment.id, &method(:rename_data_to_metrics))
+    metrics&.merge(deployment_time: deployment.created_at.to_i) || {}
+  end
+
+  def additional_environment_metrics(environment)
+    with_reactive_cache(Gitlab::Prometheus::Queries::AdditionalMetricsEnvironmentQuery.name, environment.id, &:itself)
+  end
+
+  def additional_deployment_metrics(deployment)
+    with_reactive_cache(Gitlab::Prometheus::Queries::AdditionalMetricsDeploymentQuery.name, deployment.id, &:itself)
+  end
+
+  def matched_metrics
+    with_reactive_cache(Gitlab::Prometheus::Queries::MatchedMetricsQuery.name, &:itself)
   end
 
   # Cache metrics for specific environment
   def calculate_reactive_cache(query_class_name, *args)
     return unless active? && project && !project.pending_delete?
 
-    metrics = Kernel.const_get(query_class_name).new(client).query(*args)
-
+    data = Kernel.const_get(query_class_name).new(client).query(*args)
     {
       success: true,
-      metrics: metrics,
+      data: data,
       last_update: Time.now.utc
     }
   rescue Gitlab::PrometheusError => err
@@ -91,4 +92,11 @@ class PrometheusService < MonitoringService
   def client
     @prometheus ||= Gitlab::PrometheusClient.new(api_url: api_url)
   end
+
+  private
+
+  def rename_data_to_metrics(metrics)
+    metrics[:metrics] = metrics.delete :data
+    metrics
+  end
 end
diff --git a/app/models/project_services/slack_slash_commands_service.rb b/app/models/project_services/slack_slash_commands_service.rb
index 2182c1c7e4b733ba6db50c0319ce54a8f6973d16..1c3892a3f75d579f3e04db5f5a95313954af1bbd 100644
--- a/app/models/project_services/slack_slash_commands_service.rb
+++ b/app/models/project_services/slack_slash_commands_service.rb
@@ -1,4 +1,4 @@
-class SlackSlashCommandsService < ChatSlashCommandsService
+class SlackSlashCommandsService < SlashCommandsService
   include TriggersHelper
 
   def title
diff --git a/app/models/project_services/chat_slash_commands_service.rb b/app/models/project_services/slash_commands_service.rb
similarity index 84%
rename from app/models/project_services/chat_slash_commands_service.rb
rename to app/models/project_services/slash_commands_service.rb
index 8b5bc24fd3c6b047df4365e7e125e41ade56e85a..4592cb747a0bbf5df6d257aaee28476ddaa01344 100644
--- a/app/models/project_services/chat_slash_commands_service.rb
+++ b/app/models/project_services/slash_commands_service.rb
@@ -1,6 +1,6 @@
 # Base class for Chat services
 # This class is not meant to be used directly, but only to inherrit from.
-class ChatSlashCommandsService < Service
+class SlashCommandsService < Service
   default_value_for :category, 'chat'
 
   prop_accessor :token
@@ -33,10 +33,10 @@ class ChatSlashCommandsService < Service
     user = find_chat_user(params)
 
     if user
-      Gitlab::ChatCommands::Command.new(project, user, params).execute
+      Gitlab::SlashCommands::Command.new(project, user, params).execute
     else
       url = authorize_chat_name_url(params)
-      Gitlab::ChatCommands::Presenters::Access.new(url).authorize
+      Gitlab::SlashCommands::Presenters::Access.new(url).authorize
     end
   end
 
diff --git a/app/models/project_team.rb b/app/models/project_team.rb
index e1cc56551bae324b5841703ed6a880dbb09b8d5a..674eacd28e8e143e3ce3547a5db8def792156081 100644
--- a/app/models/project_team.rb
+++ b/app/models/project_team.rb
@@ -172,10 +172,10 @@ class ProjectTeam
 
     return access if user_ids.empty?
 
-    users_access = project.project_authorizations.
-      where(user: user_ids).
-      group(:user_id).
-      maximum(:access_level)
+    users_access = project.project_authorizations
+      .where(user: user_ids)
+      .group(:user_id)
+      .maximum(:access_level)
 
     access.merge!(users_access)
 
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index f38fbda78393449c1ba6cb3cc627421699ba19c3..beaadbbd1ab118e8cdad1bc21cc2ffe5c129870b 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -31,7 +31,7 @@ class ProjectWiki
   end
 
   def web_url
-    Gitlab::Routing.url_helpers.namespace_project_wiki_url(@project.namespace, @project, :home)
+    Gitlab::Routing.url_helpers.project_wiki_url(@project, :home)
   end
 
   def url_to_repo
@@ -149,6 +149,10 @@ class ProjectWiki
     wiki
   end
 
+  def ensure_repository
+    create_repo! unless repository_exists?
+  end
+
   def hook_attrs
     {
       web_url: web_url,
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 7460515fea8c166da3f43ce65ce0a7ec2aca05f3..10b429c707ebe14ce1622131fc5a33ae88ae9ad9 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -241,11 +241,11 @@ class Repository
     cache.fetch(:"diverging_commit_counts_#{branch.name}") do
       # Rugged seems to throw a `ReferenceError` when given branch_names rather
       # than SHA-1 hashes
-      number_commits_behind = raw_repository.
-        count_commits_between(branch.dereferenced_target.sha, root_ref_hash)
+      number_commits_behind = raw_repository
+        .count_commits_between(branch.dereferenced_target.sha, root_ref_hash)
 
-      number_commits_ahead = raw_repository.
-        count_commits_between(root_ref_hash, branch.dereferenced_target.sha)
+      number_commits_ahead = raw_repository
+        .count_commits_between(root_ref_hash, branch.dereferenced_target.sha)
 
       { behind: number_commits_behind, ahead: number_commits_ahead }
     end
@@ -605,22 +605,6 @@ class Repository
     end
   end
 
-  # Returns url for submodule
-  #
-  # Ex.
-  #   @repository.submodule_url_for('master', 'rack')
-  #   # => git@localhost:rack.git
-  #
-  def submodule_url_for(ref, path)
-    if submodules(ref).any?
-      submodule = submodules(ref)[path]
-
-      if submodule
-        submodule['url']
-      end
-    end
-  end
-
   def last_commit_for_path(sha, path)
     sha = last_commit_id_for_path(sha, path)
     commit(sha)
@@ -947,7 +931,7 @@ class Repository
 
   def is_ancestor?(ancestor_id, descendant_id)
     return false if ancestor_id.nil? || descendant_id.nil?
-    
+
     Gitlab::GitalyClient.migrate(:is_ancestor) do |is_enabled|
       if is_enabled
         raw_repository.is_ancestor?(ancestor_id, descendant_id)
@@ -1094,8 +1078,8 @@ class Repository
     blob_data_at(sha, '.gitlab/route-map.yml')
   end
 
-  def gitlab_ci_yml_for(sha)
-    blob_data_at(sha, '.gitlab-ci.yml')
+  def gitlab_ci_yml_for(sha, path = '.gitlab-ci.yml')
+    blob_data_at(sha, path)
   end
 
   private
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index 54014df43b03b4d4d7a028f28f7af09a2fbbca06..b3aa7bb986e51e4c2a033b8d57b67ca7a61d262d 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -37,9 +37,7 @@ class Snippet < ActiveRecord::Base
   validates :author, presence: true
   validates :title, presence: true, length: { maximum: 255 }
   validates :file_name,
-    length: { maximum: 255 },
-    format: { with: Gitlab::Regex.file_name_regex,
-              message: Gitlab::Regex.file_name_regex_message }
+    length: { maximum: 255 }
 
   validates :content, presence: true
   validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.values }
diff --git a/app/models/todo.rb b/app/models/todo.rb
index 696d139af7485b86a6090f028df48bb8f3a29273..7af54b2beb25f7918ad4cc15e95c167fdba32f9a 100644
--- a/app/models/todo.rb
+++ b/app/models/todo.rb
@@ -70,9 +70,9 @@ class Todo < ActiveRecord::Base
 
       highest_priority = highest_label_priority(params).to_sql
 
-      select("#{table_name}.*, (#{highest_priority}) AS highest_priority").
-        order(Gitlab::Database.nulls_last_order('highest_priority', 'ASC')).
-        order('todos.created_at')
+      select("#{table_name}.*, (#{highest_priority}) AS highest_priority")
+        .order(Gitlab::Database.nulls_last_order('highest_priority', 'ASC'))
+        .order('todos.created_at')
     end
   end
 
diff --git a/app/models/user.rb b/app/models/user.rb
index 5d128e4b390fd1de8321b1ac3b96e3dddfad9f34..0febae84873bf0e73fc32a2c6a7066c198df8f3b 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -11,6 +11,7 @@ class User < ActiveRecord::Base
   include CaseSensitivity
   include TokenAuthenticatable
   include IgnorableColumn
+  include FeatureGate
 
   DEFAULT_NOTIFICATION_LEVEL = :participating
 
@@ -53,7 +54,7 @@ class User < ActiveRecord::Base
     lease = Gitlab::ExclusiveLease.new("user_update_tracked_fields:#{id}", timeout: 1.hour.to_i)
     return unless lease.try_obtain
 
-    save(validate: false)
+    Users::UpdateService.new(self).execute(validate: false)
   end
 
   attr_accessor :force_random_password
@@ -139,21 +140,21 @@ class User < ActiveRecord::Base
     presence: true,
     uniqueness: { case_sensitive: false }
 
-  validate :namespace_uniq, if: ->(user) { user.username_changed? }
+  validate :namespace_uniq, if: :username_changed?
   validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
-  validate :unique_email, if: ->(user) { user.email_changed? }
-  validate :owns_notification_email, if: ->(user) { user.notification_email_changed? }
-  validate :owns_public_email, if: ->(user) { user.public_email_changed? }
+  validate :unique_email, if: :email_changed?
+  validate :owns_notification_email, if: :notification_email_changed?
+  validate :owns_public_email, if: :public_email_changed?
   validate :signup_domain_valid?, on: :create, if: ->(user) { !user.created_by_id }
   validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
 
   before_validation :sanitize_attrs
-  before_validation :set_notification_email, if: ->(user) { user.email_changed? }
-  before_validation :set_public_email, if: ->(user) { user.public_email_changed? }
+  before_validation :set_notification_email, if: :email_changed?
+  before_validation :set_public_email, if: :public_email_changed?
 
-  after_update :update_emails_with_primary_email, if: ->(user) { user.email_changed? }
+  after_update :update_emails_with_primary_email, if: :email_changed?
   before_save :ensure_authentication_token, :ensure_incoming_email_token
-  before_save :ensure_external_user_rights
+  before_save :ensure_user_rights_and_limits, if: :external_changed?
   after_save :ensure_namespace_correct
   after_initialize :set_projects_limit
   after_destroy :post_destroy_hook
@@ -223,13 +224,13 @@ class User < ActiveRecord::Base
   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").
-      where("u2f.id IS NOT NULL OR otp_required_for_login = ?", true).distinct(arel_table[:id])
+    joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id")
+      .where("u2f.id IS NOT NULL OR otp_required_for_login = ?", true).distinct(arel_table[:id])
   end
 
   def self.without_two_factor
-    joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id").
-      where("u2f.id IS NULL AND otp_required_for_login = ?", false)
+    joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id")
+      .where("u2f.id IS NULL AND otp_required_for_login = ?", false)
   end
 
   #
@@ -299,11 +300,20 @@ class User < ActiveRecord::Base
       table   = arel_table
       pattern = "%#{query}%"
 
+      order = <<~SQL
+        CASE
+          WHEN users.name = %{query} THEN 0
+          WHEN users.username = %{query} THEN 1
+          WHEN users.email = %{query} THEN 2
+          ELSE 3
+        END
+      SQL
+
       where(
-        table[:name].matches(pattern).
-          or(table[:email].matches(pattern)).
-          or(table[:username].matches(pattern))
-      )
+        table[:name].matches(pattern)
+          .or(table[:email].matches(pattern))
+          .or(table[:username].matches(pattern))
+      ).reorder(order % { query: ActiveRecord::Base.connection.quote(query) }, id: :desc)
     end
 
     # searches user by given pattern
@@ -317,10 +327,10 @@ class User < ActiveRecord::Base
       matched_by_emails_user_ids = email_table.project(email_table[:user_id]).where(email_table[:email].matches(pattern))
 
       where(
-        table[:name].matches(pattern).
-          or(table[:email].matches(pattern)).
-          or(table[:username].matches(pattern)).
-          or(table[:id].in(matched_by_emails_user_ids))
+        table[:name].matches(pattern)
+          .or(table[:email].matches(pattern))
+          .or(table[:username].matches(pattern))
+          .or(table[:id].in(matched_by_emails_user_ids))
       )
     end
 
@@ -494,17 +504,15 @@ class User < ActiveRecord::Base
   def update_emails_with_primary_email
     primary_email_record = emails.find_by(email: email)
     if primary_email_record
-      primary_email_record.destroy
-      emails.create(email: email_was)
-
-      update_secondary_emails!
+      Emails::DestroyService.new(self, email: email).execute
+      Emails::CreateService.new(self, email: email_was).execute
     end
   end
 
   # Returns the groups a user has access to
   def authorized_groups
-    union = Gitlab::SQL::Union.
-      new([groups.select(:id), authorized_projects.select(:namespace_id)])
+    union = Gitlab::SQL::Union
+      .new([groups.select(:id), authorized_projects.select(:namespace_id)])
 
     Group.where("namespaces.id IN (#{union.to_sql})")
   end
@@ -533,8 +541,8 @@ class User < ActiveRecord::Base
     projects = super()
 
     if min_access_level
-      projects = projects.
-        where('project_authorizations.access_level >= ?', min_access_level)
+      projects = projects
+        .where('project_authorizations.access_level >= ?', min_access_level)
     end
 
     projects
@@ -572,7 +580,13 @@ class User < ActiveRecord::Base
   end
 
   def require_password?
-    password_automatically_set? && !ldap_user?
+    password_automatically_set? && !ldap_user? && current_application_settings.signin_enabled?
+  end
+
+  def require_personal_access_token?
+    return false if current_application_settings.signin_enabled? || ldap_user?
+
+    PersonalAccessTokensFinder.new(user: self, impersonation: false, state: 'active').execute.none?
   end
 
   def can_change_username?
@@ -619,9 +633,9 @@ class User < ActiveRecord::Base
       next unless project
 
       if project.repository.branch_exists?(event.branch_name)
-        merge_requests = MergeRequest.where("created_at >= ?", event.created_at).
-          where(source_project_id: project.id,
-                source_branch: event.branch_name)
+        merge_requests = MergeRequest.where("created_at >= ?", event.created_at)
+          .where(source_project_id: project.id,
+                 source_branch: event.branch_name)
         merge_requests.empty?
       end
     end
@@ -832,8 +846,8 @@ class User < ActiveRecord::Base
 
   def toggle_star(project)
     UsersStarProject.transaction do
-      user_star_project = users_star_projects.
-          where(project: project, user: self).lock(true).first
+      user_star_project = users_star_projects
+          .where(project: project, user: self).lock(true).first
 
       if user_star_project
         user_star_project.destroy
@@ -869,11 +883,11 @@ class User < ActiveRecord::Base
   # ms on a database with a similar size to GitLab.com's database. On the other
   # hand, using a subquery means we can get the exact same data in about 40 ms.
   def contributed_projects
-    events = Event.select(:project_id).
-      contributions.where(author_id: self).
-      where("created_at > ?", Time.now - 1.year).
-      uniq.
-      reorder(nil)
+    events = Event.select(:project_id)
+      .contributions.where(author_id: self)
+      .where("created_at > ?", Time.now - 1.year)
+      .uniq
+      .reorder(nil)
 
     Project.where(id: events)
   end
@@ -884,9 +898,9 @@ class User < ActiveRecord::Base
 
   def ci_authorized_runners
     @ci_authorized_runners ||= begin
-      runner_ids = Ci::RunnerProject.
-        where("ci_runner_projects.project_id IN (#{ci_projects_union.to_sql})").
-        select(:runner_id)
+      runner_ids = Ci::RunnerProject
+        .where("ci_runner_projects.project_id IN (#{ci_projects_union.to_sql})")
+        .select(:runner_id)
       Ci::Runner.specific.where(id: runner_ids)
     end
   end
@@ -965,7 +979,7 @@ class User < ActiveRecord::Base
     if attempts_exceeded?
       lock_access! unless access_locked?
     else
-      save(validate: false)
+      Users::UpdateService.new(self).execute(validate: false)
     end
   end
 
@@ -984,6 +998,12 @@ class User < ActiveRecord::Base
     self.admin = (new_level == 'admin')
   end
 
+  # Does the user have access to all private groups & projects?
+  # Overridden in EE to also check auditor?
+  def full_private_access?
+    admin?
+  end
+
   def update_two_factor_requirement
     periods = expanded_groups_requiring_two_factor_authentication.pluck(:two_factor_grace_period)
 
@@ -1033,11 +1053,14 @@ class User < ActiveRecord::Base
     super
   end
 
-  def ensure_external_user_rights
-    return unless external?
-
-    self.can_create_group   = false
-    self.projects_limit     = 0
+  def ensure_user_rights_and_limits
+    if external?
+      self.can_create_group = false
+      self.projects_limit   = 0
+    else
+      self.can_create_group = gitlab_config.default_can_create_group
+      self.projects_limit = current_application_settings.default_projects_limit
+    end
   end
 
   def signup_domain_valid?
@@ -1120,7 +1143,8 @@ class User < ActiveRecord::Base
       email: email,
       &creation_block
     )
-    user.save(validate: false)
+
+    Users::UpdateService.new(user).execute(validate: false)
     user
   ensure
     Gitlab::ExclusiveLease.cancel(lease_key, uuid)
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index c771c22f46a1620b37986d738eedac588be66cfc..224eb3cd4d01973a09e2c5eacb8b0a920f69d6ef 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -22,16 +22,16 @@ class WikiPage
   def self.group_by_directory(pages)
     return [] if pages.blank?
 
-    pages.sort_by { |page| [page.directory, page.slug] }.
-      group_by(&:directory).
-      map do |dir, pages|
+    pages.sort_by { |page| [page.directory, page.slug] }
+      .group_by(&:directory)
+      .map do |dir, pages|
         if dir.present?
           WikiDirectory.new(dir, pages)
         else
           pages
         end
-      end.
-      flatten
+      end
+      .flatten
   end
 
   def self.unhyphenize(name)
diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb
index 623424c63e087aebbbba35eb47f90a4453da74b6..a605a3457c8d7f4ce2cc3f6818ba3ab1262600ca 100644
--- a/app/policies/base_policy.rb
+++ b/app/policies/base_policy.rb
@@ -1,127 +1,20 @@
-class BasePolicy
-  class RuleSet
-    attr_reader :can_set, :cannot_set
-    def initialize(can_set, cannot_set)
-      @can_set = can_set
-      @cannot_set = cannot_set
-    end
+require_dependency 'declarative_policy'
 
-    delegate :size, to: :to_set
+class BasePolicy < DeclarativePolicy::Base
+  include Gitlab::CurrentSettings
 
-    def self.empty
-      new(Set.new, Set.new)
-    end
+  desc "User is an instance admin"
+  with_options scope: :user, score: 0
+  condition(:admin) { @user&.admin? }
 
-    def self.none
-      empty.freeze
-    end
+  with_options scope: :user, score: 0
+  condition(:external_user) { @user.nil? || @user.external? }
 
-    def can?(ability)
-      @can_set.include?(ability) && !@cannot_set.include?(ability)
-    end
+  with_options scope: :user, score: 0
+  condition(:can_create_group) { @user&.can_create_group }
 
-    def include?(ability)
-      can?(ability)
-    end
-
-    def to_set
-      @can_set - @cannot_set
-    end
-
-    def merge(other)
-      @can_set.merge(other.can_set)
-      @cannot_set.merge(other.cannot_set)
-    end
-
-    def can!(*abilities)
-      @can_set.merge(abilities)
-    end
-
-    def cannot!(*abilities)
-      @cannot_set.merge(abilities)
-    end
-
-    def freeze
-      @can_set.freeze
-      @cannot_set.freeze
-      super
-    end
-  end
-
-  def self.abilities(user, subject)
-    new(user, subject).abilities
-  end
-
-  def self.class_for(subject)
-    return GlobalPolicy if subject == :global
-    raise ArgumentError, 'no policy for nil' if subject.nil?
-
-    if subject.class.try(:presenter?)
-      subject = subject.subject
-    end
-
-    subject.class.ancestors.each do |klass|
-      next unless klass.name
-
-      begin
-        policy_class = "#{klass.name}Policy".constantize
-
-        # NOTE: the < operator here tests whether policy_class
-        # inherits from BasePolicy
-        return policy_class if policy_class < BasePolicy
-      rescue NameError
-        nil
-      end
-    end
-
-    raise "no policy for #{subject.class.name}"
-  end
-
-  attr_reader :user, :subject
-  def initialize(user, subject)
-    @user = user
-    @subject = subject
-  end
-
-  def abilities
-    return RuleSet.none if @user && @user.blocked?
-    return anonymous_abilities if @user.nil?
-    collect_rules { rules }
-  end
-
-  def anonymous_abilities
-    collect_rules { anonymous_rules }
-  end
-
-  def anonymous_rules
-    rules
-  end
-
-  def rules
-    raise NotImplementedError
-  end
-
-  def delegate!(new_subject)
-    @rule_set.merge(Ability.allowed(@user, new_subject))
-  end
-
-  def can?(rule)
-    @rule_set.can?(rule)
-  end
-
-  def can!(*rules)
-    @rule_set.can!(*rules)
-  end
-
-  def cannot!(*rules)
-    @rule_set.cannot!(*rules)
-  end
-
-  private
-
-  def collect_rules(&b)
-    @rule_set = RuleSet.empty
-    yield
-    @rule_set
+  desc "The application is restricted from public visibility"
+  condition(:restricted_public_level, scope: :global) do
+    current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
   end
 end
diff --git a/app/policies/ci/build_policy.rb b/app/policies/ci/build_policy.rb
index 2d7405dc2403446061246f110fceeaa71e98b831..a886efc13601136a8671821a45e93d074ec7f3fc 100644
--- a/app/policies/ci/build_policy.rb
+++ b/app/policies/ci/build_policy.rb
@@ -1,29 +1,13 @@
 module Ci
   class BuildPolicy < CommitStatusPolicy
-    alias_method :build, :subject
-
-    def rules
-      super
-
-      # If we can't read build we should also not have that
-      # ability when looking at this in context of commit_status
-      %w[read create update admin].each do |rule|
-        cannot! :"#{rule}_commit_status" unless can? :"#{rule}_build"
-      end
-
-      if can?(:update_build) && protected_action?
-        cannot! :update_build
-      end
-    end
-
-    private
-
-    def protected_action?
-      return false unless build.action?
+    condition(:protected_action) do
+      next false unless @subject.action?
 
       !::Gitlab::UserAccess
-        .new(user, project: build.project)
-        .can_merge_to_branch?(build.ref)
+        .new(@user, project: @subject.project)
+        .can_merge_to_branch?(@subject.ref)
     end
+
+    rule { protected_action }.prevent :update_build
   end
 end
diff --git a/app/policies/ci/pipeline_policy.rb b/app/policies/ci/pipeline_policy.rb
index 10aa2d3e72a5d857ff51d0c831cda108d8105786..a2dde95dbc8a1897229383b6e8a82eb8d15fd122 100644
--- a/app/policies/ci/pipeline_policy.rb
+++ b/app/policies/ci/pipeline_policy.rb
@@ -1,7 +1,5 @@
 module Ci
   class PipelinePolicy < BasePolicy
-    def rules
-      delegate! @subject.project
-    end
+    delegate { @subject.project }
   end
 end
diff --git a/app/policies/ci/runner_policy.rb b/app/policies/ci/runner_policy.rb
index 416d93ffe630995a924268e68f22ea2c2165f724..7dff8470e23b31cd0684f343bf9aa0aabd810cb6 100644
--- a/app/policies/ci/runner_policy.rb
+++ b/app/policies/ci/runner_policy.rb
@@ -1,13 +1,16 @@
 module Ci
   class RunnerPolicy < BasePolicy
-    def rules
-      return unless @user
+    with_options scope: :subject, score: 0
+    condition(:shared) { @subject.is_shared? }
 
-      can! :assign_runner if @user.admin?
+    with_options scope: :subject, score: 0
+    condition(:locked, scope: :subject) { @subject.locked? }
 
-      return if @subject.is_shared? || @subject.locked?
+    condition(:authorized_runner) { @user.ci_authorized_runners.include?(@subject) }
 
-      can! :assign_runner if @user.ci_authorized_runners.include?(@subject)
-    end
+    rule { anonymous }.prevent_all
+    rule { admin | authorized_runner }.enable :assign_runner
+    rule { ~admin & shared }.prevent :assign_runner
+    rule { ~admin & locked }.prevent :assign_runner
   end
 end
diff --git a/app/policies/ci/trigger_policy.rb b/app/policies/ci/trigger_policy.rb
index c90c9ac058374f5ca580a733d7d1f1dbfef353f6..5592ac3081218e4fb3bf08fed43440af97e3189a 100644
--- a/app/policies/ci/trigger_policy.rb
+++ b/app/policies/ci/trigger_policy.rb
@@ -1,13 +1,16 @@
 module Ci
   class TriggerPolicy < BasePolicy
-    def rules
-      delegate! @subject.project
-
-      if can?(:admin_build)
-        can! :admin_trigger if @subject.owner.blank? ||
-            @subject.owner == @user
-        can! :manage_trigger
-      end
-    end
+    delegate { @subject.project }
+
+    with_options scope: :subject, score: 0
+    condition(:legacy) { @subject.legacy? }
+
+    with_score 0
+    condition(:is_owner) { @user && @subject.owner_id == @user.id }
+
+    rule { ~can?(:admin_build) }.prevent :admin_trigger
+    rule { legacy | is_owner }.enable :admin_trigger
+
+    rule { can?(:admin_build) }.enable :manage_trigger
   end
 end
diff --git a/app/policies/commit_status_policy.rb b/app/policies/commit_status_policy.rb
index 593df738328045f390787de45c8ff54fc2b3d9a2..24b2a4cc7fd7bb37cc51f86cc365cf1e975a1a74 100644
--- a/app/policies/commit_status_policy.rb
+++ b/app/policies/commit_status_policy.rb
@@ -1,5 +1,7 @@
 class CommitStatusPolicy < BasePolicy
-  def rules
-    delegate! @subject.project
+  delegate { @subject.project }
+
+  %w[read create update admin].each do |action|
+    rule { ~can?(:"#{action}_commit_status") }.prevent :"#{action}_build"
   end
 end
diff --git a/app/policies/deploy_key_policy.rb b/app/policies/deploy_key_policy.rb
index ebab213e6be6f588ab20ba6110428613c6fa84ae..62a22a59be6e93d32aafd085faf7b46be6aff94f 100644
--- a/app/policies/deploy_key_policy.rb
+++ b/app/policies/deploy_key_policy.rb
@@ -1,11 +1,11 @@
 class DeployKeyPolicy < BasePolicy
-  def rules
-    return unless @user
+  with_options scope: :subject, score: 0
+  condition(:private_deploy_key) { @subject.private? }
 
-    can! :update_deploy_key if @user.admin?
+  condition(:has_deploy_key) { @user.project_deploy_keys.exists?(id: @subject.id) }
 
-    if @subject.private? && @user.project_deploy_keys.exists?(id: @subject.id)
-      can! :update_deploy_key
-    end
-  end
+  rule { anonymous }.prevent_all
+
+  rule { admin }.enable :update_deploy_key
+  rule { private_deploy_key & has_deploy_key }.enable :update_deploy_key
 end
diff --git a/app/policies/deployment_policy.rb b/app/policies/deployment_policy.rb
index 163d070ff903447ac5929a95131d9d892bc8a3f3..62b63b9f87bb73dbe485f64e54d155608906fe1b 100644
--- a/app/policies/deployment_policy.rb
+++ b/app/policies/deployment_policy.rb
@@ -1,5 +1,3 @@
 class DeploymentPolicy < BasePolicy
-  def rules
-    delegate! @subject.project
-  end
+  delegate { @subject.project }
 end
diff --git a/app/policies/environment_policy.rb b/app/policies/environment_policy.rb
index 2fa15e645629ba69cf14112a380e64f3631ff771..375a553535984e89782e305199cbb7e0e04656c8 100644
--- a/app/policies/environment_policy.rb
+++ b/app/policies/environment_policy.rb
@@ -1,17 +1,9 @@
 class EnvironmentPolicy < BasePolicy
-  alias_method :environment, :subject
+  delegate { @subject.project }
 
-  def rules
-    delegate! environment.project
-
-    if can?(:create_deployment) && environment.stop_action?
-      can! :stop_environment if can_play_stop_action?
-    end
+  condition(:stop_action_allowed) do
+    @subject.stop_action? && can?(:update_build, @subject.stop_action)
   end
 
-  private
-
-  def can_play_stop_action?
-    Ability.allowed?(user, :update_build, environment.stop_action)
-  end
+  rule { can?(:create_deployment) & stop_action_allowed }.enable :stop_environment
 end
diff --git a/app/policies/external_issue_policy.rb b/app/policies/external_issue_policy.rb
index d9e28bd107a25699ac8c7eb69b23bda25bb9c0ff..e031b38078cec3dae6303eb89f415c9f98ad2ed3 100644
--- a/app/policies/external_issue_policy.rb
+++ b/app/policies/external_issue_policy.rb
@@ -1,5 +1,3 @@
 class ExternalIssuePolicy < BasePolicy
-  def rules
-    delegate! @subject.project
-  end
+  delegate { @subject.project }
 end
diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb
index 4757ba7168089776b1bb64d5943704095e2d5022..55eefa76d3f33bd0a93caeb752fd3f51d4e0b27c 100644
--- a/app/policies/global_policy.rb
+++ b/app/policies/global_policy.rb
@@ -1,16 +1,50 @@
 class GlobalPolicy < BasePolicy
-  def rules
-    return unless @user
-
-    can! :create_group if @user.can_create_group
-    can! :read_users_list
-
-    unless @user.blocked? || @user.internal?
-      can! :log_in unless @user.access_locked?
-      can! :access_api
-      can! :access_git
-      can! :receive_notifications
-      can! :use_slash_commands
-    end
+  desc "User is blocked"
+  with_options scope: :user, score: 0
+  condition(:blocked) { @user.blocked? }
+
+  desc "User is an internal user"
+  with_options scope: :user, score: 0
+  condition(:internal) { @user.internal? }
+
+  desc "User's access has been locked"
+  with_options scope: :user, score: 0
+  condition(:access_locked) { @user.access_locked? }
+
+  rule { anonymous }.policy do
+    prevent :log_in
+    prevent :access_api
+    prevent :access_git
+    prevent :receive_notifications
+    prevent :use_quick_actions
+    prevent :create_group
+  end
+
+  rule { default }.policy do
+    enable :log_in
+    enable :access_api
+    enable :access_git
+    enable :receive_notifications
+    enable :use_quick_actions
+  end
+
+  rule { blocked | internal }.policy do
+    prevent :log_in
+    prevent :access_api
+    prevent :access_git
+    prevent :receive_notifications
+    prevent :use_quick_actions
+  end
+
+  rule { can_create_group }.policy do
+    enable :create_group
+  end
+
+  rule { access_locked }.policy do
+    prevent :log_in
+  end
+
+  rule { ~restricted_public_level }.policy do
+    enable :read_users_list
   end
 end
diff --git a/app/policies/group_label_policy.rb b/app/policies/group_label_policy.rb
index 7b34aa182eb4edc6cf461354b42ae189e588b514..e3dd3296699250fa4391581f2b0ec1759d95ed8e 100644
--- a/app/policies/group_label_policy.rb
+++ b/app/policies/group_label_policy.rb
@@ -1,5 +1,3 @@
 class GroupLabelPolicy < BasePolicy
-  def rules
-    delegate! @subject.group
-  end
+  delegate { @subject.group }
 end
diff --git a/app/policies/group_member_policy.rb b/app/policies/group_member_policy.rb
index 5a3fe814b77950a685e3b06f10b3a1966fc53e07..23dd0d7cd23827d3a0089a24241171df29fccecf 100644
--- a/app/policies/group_member_policy.rb
+++ b/app/policies/group_member_policy.rb
@@ -1,25 +1,22 @@
 class GroupMemberPolicy < BasePolicy
-  def rules
-    return unless @user
+  delegate :group
 
-    target_user = @subject.user
-    group = @subject.group
+  with_scope :subject
+  condition(:last_owner) { @subject.group.last_owner?(@subject.user) }
 
-    return if group.last_owner?(target_user)
+  desc "Membership is users' own"
+  with_score 0
+  condition(:is_target_user) { @user && @subject.user_id == @user.id }
 
-    can_manage = Ability.allowed?(@user, :admin_group_member, group)
+  rule { anonymous }.prevent_all
+  rule { last_owner }.prevent_all
 
-    if can_manage
-      can! :update_group_member
-      can! :destroy_group_member
-    elsif @user == target_user
-      can! :destroy_group_member
-    end
-
-    additional_rules!
+  rule { can?(:admin_group_member) }.policy do
+    enable :update_group_member
+    enable :destroy_group_member
   end
 
-  def additional_rules!
-    # This is meant to be overriden in EE
+  rule { is_target_user }.policy do
+    enable :destroy_group_member
   end
 end
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
index fb07298c6c299c798bf8aa299a0067aba0f676bd..dcb37416ca3756e799c11d0a3c860d01840e7a68 100644
--- a/app/policies/group_policy.rb
+++ b/app/policies/group_policy.rb
@@ -1,50 +1,58 @@
 class GroupPolicy < BasePolicy
-  def rules
-    can! :read_group if @subject.public?
-    return unless @user
-
-    globally_viewable = @subject.public? || (@subject.internal? && !@user.external?)
-    access_level = @subject.max_member_access_for_user(@user)
-    owner = access_level >= GroupMember::OWNER
-    master = access_level >= GroupMember::MASTER
-    reporter = access_level >= GroupMember::REPORTER
-
-    can_read = false
-    can_read ||= globally_viewable
-    can_read ||= access_level >= GroupMember::GUEST
-    can_read ||= GroupProjectsFinder.new(group: @subject, current_user: @user).execute.any?
-    can! :read_group if can_read
-
-    if reporter
-      can! :admin_label
-    end
-
-    # Only group masters and group owners can create new projects
-    if master
-      can! :create_projects
-      can! :admin_milestones
-    end
-
-    # Only group owner and administrators can admin group
-    if owner
-      can! :admin_group
-      can! :admin_namespace
-      can! :admin_group_member
-      can! :change_visibility_level
-      can! :create_subgroup if @user.can_create_group
-    end
-
-    if globally_viewable && @subject.request_access_enabled && access_level == GroupMember::NO_ACCESS
-      can! :request_access
-    end
-  end
+  desc "Group is public"
+  with_options scope: :subject, score: 0
+  condition(:public_group) { @subject.public? }
+
+  with_score 0
+  condition(:logged_in_viewable) { @user && @subject.internal? && !@user.external? }
+
+  condition(:has_access) { access_level != GroupMember::NO_ACCESS }
 
-  def can_read_group?
-    return true if @subject.public?
-    return true if @user.admin?
-    return true if @subject.internal? && !@user.external?
-    return true if @subject.users.include?(@user)
+  condition(:guest) { access_level >= GroupMember::GUEST }
+  condition(:owner) { access_level >= GroupMember::OWNER }
+  condition(:master) { access_level >= GroupMember::MASTER }
+  condition(:reporter) { access_level >= GroupMember::REPORTER }
 
+  condition(:has_projects) do
     GroupProjectsFinder.new(group: @subject, current_user: @user).execute.any?
   end
+
+  with_options scope: :subject, score: 0
+  condition(:request_access_enabled) { @subject.request_access_enabled }
+
+  rule { public_group }      .enable :read_group
+  rule { logged_in_viewable }.enable :read_group
+  rule { guest }             .enable :read_group
+  rule { admin }             .enable :read_group
+  rule { has_projects }      .enable :read_group
+
+  rule { reporter }.enable :admin_label
+
+  rule { master }.policy do
+    enable :create_projects
+    enable :admin_milestones
+  end
+
+  rule { owner }.policy do
+    enable :admin_group
+    enable :admin_namespace
+    enable :admin_group_member
+    enable :change_visibility_level
+  end
+
+  rule { owner & can_create_group }.enable :create_subgroup
+
+  rule { public_group | logged_in_viewable }.enable :view_globally
+
+  rule { default }.enable(:request_access)
+
+  rule { ~request_access_enabled }.prevent :request_access
+  rule { ~can?(:view_globally) }.prevent   :request_access
+  rule { has_access }.prevent              :request_access
+
+  def access_level
+    return GroupMember::NO_ACCESS if @user.nil?
+
+    @access_level ||= @subject.max_member_access_for_user(@user)
+  end
 end
diff --git a/app/policies/issuable_policy.rb b/app/policies/issuable_policy.rb
index 9501e499507b966aa7086e351647a77aa2e096c8..daf6fa9e18acd5c1cfeb9be7ae302e0217ca8a01 100644
--- a/app/policies/issuable_policy.rb
+++ b/app/policies/issuable_policy.rb
@@ -1,14 +1,15 @@
 class IssuablePolicy < BasePolicy
-  def action_name
-    @subject.class.name.underscore
-  end
+  delegate { @subject.project }
 
-  def rules
-    if @user && @subject.assignee_or_author?(@user)
-      can! :"read_#{action_name}"
-      can! :"update_#{action_name}"
-    end
+  desc "User is the assignee or author"
+  condition(:assignee_or_author) do
+    @user && @subject.assignee_or_author?(@user)
+  end
 
-    delegate! @subject.project
+  rule { assignee_or_author }.policy do
+    enable :read_issue
+    enable :update_issue
+    enable :read_merge_request
+    enable :update_merge_request
   end
 end
diff --git a/app/policies/issue_policy.rb b/app/policies/issue_policy.rb
index 88f3179c6fff6c763a0bf6a62562f85d519292a0..bd2d417b2a8ee82896cabbc2f327532cb8b53f6f 100644
--- a/app/policies/issue_policy.rb
+++ b/app/policies/issue_policy.rb
@@ -3,25 +3,17 @@ class IssuePolicy < IssuablePolicy
   # Make sure to sync this class checks with issue.rb to avoid security problems.
   # Check commit 002ad215818450d2cbbc5fa065850a953dc7ada8 for more information.
 
-  def issue
-    @subject
+  desc "User can read confidential issues"
+  condition(:can_read_confidential) do
+    @user && IssueCollection.new([@subject]).visible_to(@user).any?
   end
 
-  def rules
-    super
+  desc "Issue is confidential"
+  condition(:confidential, scope: :subject) { @subject.confidential? }
 
-    if @subject.confidential? && !can_read_confidential?
-      cannot! :read_issue
-      cannot! :update_issue
-      cannot! :admin_issue
-    end
-  end
-
-  private
-
-  def can_read_confidential?
-    return false unless @user
-
-    IssueCollection.new([@subject]).visible_to(@user).any?
+  rule { confidential & ~can_read_confidential }.policy do
+    prevent :read_issue
+    prevent :update_issue
+    prevent :admin_issue
   end
 end
diff --git a/app/policies/namespace_policy.rb b/app/policies/namespace_policy.rb
index 29bb357e00a8c63b53267d160d91974ebb62bb1b..85b67f0a237ed01b96890f87bf70bd028591fbde 100644
--- a/app/policies/namespace_policy.rb
+++ b/app/policies/namespace_policy.rb
@@ -1,10 +1,10 @@
 class NamespacePolicy < BasePolicy
-  def rules
-    return unless @user
+  rule { anonymous }.prevent_all
 
-    if @subject.owner == @user || @user.admin?
-      can! :create_projects
-      can! :admin_namespace
-    end
+  condition(:owner) { @subject.owner == @user }
+
+  rule { owner | admin }.policy do
+    enable :create_projects
+    enable :admin_namespace
   end
 end
diff --git a/app/policies/nil_policy.rb b/app/policies/nil_policy.rb
new file mode 100644
index 0000000000000000000000000000000000000000..13f46ba60f086a4ba8f7a3d5552a561f44e8560d
--- /dev/null
+++ b/app/policies/nil_policy.rb
@@ -0,0 +1,3 @@
+class NilPolicy < BasePolicy
+  rule { default }.prevent_all
+end
diff --git a/app/policies/note_policy.rb b/app/policies/note_policy.rb
index 5326061bd077f804317ea0c4f2f94647cfa9739b..20cd51cfb99adf10c893db1e7ca0521848f11d97 100644
--- a/app/policies/note_policy.rb
+++ b/app/policies/note_policy.rb
@@ -1,19 +1,24 @@
 class NotePolicy < BasePolicy
-  def rules
-    delegate! @subject.project
+  delegate { @subject.project }
 
-    return unless @user
+  condition(:is_author) { @user && @subject.author == @user }
+  condition(:for_merge_request, scope: :subject) { @subject.for_merge_request? }
+  condition(:is_noteable_author) { @user && @subject.noteable.author_id == @user.id }
 
-    if @subject.author == @user
-      can! :read_note
-      can! :update_note
-      can! :admin_note
-      can! :resolve_note
-    end
+  condition(:editable, scope: :subject) { @subject.editable? }
 
-    if @subject.for_merge_request? &&
-        @subject.noteable.author == @user
-      can! :resolve_note
-    end
+  rule { ~editable | anonymous }.prevent :edit_note
+  rule { is_author | admin }.enable :edit_note
+  rule { can?(:master_access) }.enable :edit_note
+
+  rule { is_author }.policy do
+    enable :read_note
+    enable :update_note
+    enable :admin_note
+    enable :resolve_note
+  end
+
+  rule { for_merge_request & is_noteable_author }.policy do
+    enable :resolve_note
   end
 end
diff --git a/app/policies/personal_snippet_policy.rb b/app/policies/personal_snippet_policy.rb
index e1e5336da8cd85f74de4de5488de28c9aeb17493..cac0530b9f71b696f18402a64a11553549d98cd1 100644
--- a/app/policies/personal_snippet_policy.rb
+++ b/app/policies/personal_snippet_policy.rb
@@ -1,27 +1,28 @@
 class PersonalSnippetPolicy < BasePolicy
-  def rules
-    can! :read_personal_snippet if @subject.public?
-    return unless @user
+  condition(:public_snippet, scope: :subject) { @subject.public? }
+  condition(:is_author) { @user && @subject.author == @user }
+  condition(:internal_snippet, scope: :subject) { @subject.internal? }
 
-    if @subject.public?
-      can! :comment_personal_snippet
-    end
+  rule { public_snippet }.policy do
+    enable :read_personal_snippet
+    enable :comment_personal_snippet
+  end
 
-    if @subject.author == @user
-      can! :read_personal_snippet
-      can! :update_personal_snippet
-      can! :destroy_personal_snippet
-      can! :admin_personal_snippet
-      can! :comment_personal_snippet
-    end
+  rule { is_author }.policy do
+    enable :read_personal_snippet
+    enable :update_personal_snippet
+    enable :destroy_personal_snippet
+    enable :admin_personal_snippet
+    enable :comment_personal_snippet
+  end
 
-    unless @user.external?
-      can! :create_personal_snippet
-    end
+  rule { ~anonymous }.enable :create_personal_snippet
+  rule { external_user }.prevent :create_personal_snippet
 
-    if @subject.internal? && !@user.external?
-      can! :read_personal_snippet
-      can! :comment_personal_snippet
-    end
+  rule { internal_snippet & ~external_user }.policy do
+    enable :read_personal_snippet
+    enable :comment_personal_snippet
   end
+
+  rule { anonymous }.prevent :comment_personal_snippet
 end
diff --git a/app/policies/project_label_policy.rb b/app/policies/project_label_policy.rb
index b12b4c5166b3dd7066718a6de24e24202f4ea940..2d0f021118b289188e3f7f63d89bab970c466fd2 100644
--- a/app/policies/project_label_policy.rb
+++ b/app/policies/project_label_policy.rb
@@ -1,5 +1,3 @@
 class ProjectLabelPolicy < BasePolicy
-  def rules
-    delegate! @subject.project
-  end
+  delegate { @subject.project }
 end
diff --git a/app/policies/project_member_policy.rb b/app/policies/project_member_policy.rb
index 1c038dddd4bdd0b991aed4f55921747e24a9c741..9aedb620be96a6f0c611e186e3f8e19b9ef6a725 100644
--- a/app/policies/project_member_policy.rb
+++ b/app/policies/project_member_policy.rb
@@ -1,22 +1,16 @@
 class ProjectMemberPolicy < BasePolicy
-  def rules
-    # anonymous users have no abilities here
-    return unless @user
+  delegate { @subject.project }
 
-    target_user = @subject.user
-    project = @subject.project
+  condition(:target_is_owner, scope: :subject) { @subject.user == @subject.project.owner }
+  condition(:target_is_self) { @user && @subject.user == @user }
 
-    return if target_user == project.owner
+  rule { anonymous }.prevent_all
+  rule { target_is_owner }.prevent_all
 
-    can_manage = Ability.allowed?(@user, :admin_project_member, project)
-
-    if can_manage
-      can! :update_project_member
-      can! :destroy_project_member
-    end
-
-    if @user == target_user
-      can! :destroy_project_member
-    end
+  rule { can?(:admin_project_member) }.policy do
+    enable :update_project_member
+    enable :destroy_project_member
   end
+
+  rule { target_is_self }.enable :destroy_project_member
 end
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 47518dddb61cb80a37fb7d0da419ce665a52dbfd..7cbca63fab492d0808da5d26247e0dd9820801ae 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -1,297 +1,353 @@
 class ProjectPolicy < BasePolicy
-  def rules
-    team_access!(user)
+  def self.create_read_update_admin(name)
+    [
+      :"create_#{name}",
+      :"read_#{name}",
+      :"update_#{name}",
+      :"admin_#{name}"
+    ]
+  end
 
-    owner_access! if user.admin? || owner?
-    team_member_owner_access! if owner?
+  desc "User is a project owner"
+  condition :owner do
+    @user && project.owner == @user || (project.group && project.group.has_owner?(@user))
+  end
 
-    if project.public? || (project.internal? && !user.external?)
-      guest_access!
-      public_access!
-      can! :request_access if access_requestable?
-    end
+  desc "Project has public builds enabled"
+  condition(:public_builds, scope: :subject) { project.public_builds? }
+
+  # For guest access we use #is_team_member? so we can use
+  # project.members, which gets cached in subject scope.
+  # This is safe because team_access_level is guaranteed
+  # by ProjectAuthorization's validation to be at minimum
+  # GUEST
+  desc "User has guest access"
+  condition(:guest) { is_team_member? }
 
-    archived_access! if project.archived?
+  desc "User has reporter access"
+  condition(:reporter) { team_access_level >= Gitlab::Access::REPORTER }
 
-    disabled_features!
+  desc "User has developer access"
+  condition(:developer) { team_access_level >= Gitlab::Access::DEVELOPER }
+
+  desc "User has master access"
+  condition(:master) { team_access_level >= Gitlab::Access::MASTER }
+
+  desc "Project is public"
+  condition(:public_project, scope: :subject) { project.public? }
+
+  desc "Project is visible to internal users"
+  condition(:internal_access) do
+    project.internal? && !user.external?
   end
 
-  def project
-    @subject
+  desc "User is a member of the group"
+  condition(:group_member, scope: :subject) { project_group_member? }
+
+  desc "Project is archived"
+  condition(:archived, scope: :subject) { project.archived? }
+
+  condition(:default_issues_tracker, scope: :subject) { project.default_issues_tracker? }
+
+  desc "Container registry is disabled"
+  condition(:container_registry_disabled, scope: :subject) do
+    !project.container_registry_enabled
   end
 
-  def owner?
-    return @owner if defined?(@owner)
-
-    @owner = project.owner == user ||
-      (project.group && project.group.has_owner?(user))
-  end
-
-  def guest_access!
-    can! :read_project
-    can! :read_board
-    can! :read_list
-    can! :read_wiki
-    can! :read_issue
-    can! :read_label
-    can! :read_milestone
-    can! :read_project_snippet
-    can! :read_project_member
-    can! :read_note
-    can! :create_project
-    can! :create_issue
-    can! :create_note
-    can! :upload_file
-    can! :read_cycle_analytics
-
-    if project.public_builds?
-      can! :read_pipeline
-      can! :read_pipeline_schedule
-      can! :read_build
-    end
+  desc "Project has an external wiki"
+  condition(:has_external_wiki, scope: :subject) { project.has_external_wiki? }
+
+  desc "Project has request access enabled"
+  condition(:request_access_enabled, scope: :subject) { project.request_access_enabled }
+
+  features = %w[
+    merge_requests
+    issues
+    repository
+    snippets
+    wiki
+    builds
+  ]
+
+  features.each do |f|
+    # these are scored high because they are unlikely
+    desc "Project has #{f} disabled"
+    condition(:"#{f}_disabled", score: 32) { !feature_available?(f.to_sym) }
   end
 
-  def reporter_access!
-    can! :download_code
-    can! :download_wiki_code
-    can! :fork_project
-    can! :create_project_snippet
-    can! :update_issue
-    can! :admin_issue
-    can! :admin_label
-    can! :admin_list
-    can! :read_commit_status
-    can! :read_build
-    can! :read_container_image
-    can! :read_pipeline
-    can! :read_pipeline_schedule
-    can! :read_environment
-    can! :read_deployment
-    can! :read_merge_request
-  end
-
-  # Permissions given when an user is team member of a project
-  def team_member_reporter_access!
-    can! :build_download_code
-    can! :build_read_container_image
-  end
-
-  def developer_access!
-    can! :admin_merge_request
-    can! :update_merge_request
-    can! :create_commit_status
-    can! :update_commit_status
-    can! :create_build
-    can! :update_build
-    can! :create_pipeline
-    can! :update_pipeline
-    can! :create_pipeline_schedule
-    can! :update_pipeline_schedule
-    can! :create_merge_request
-    can! :create_wiki
-    can! :push_code
-    can! :resolve_note
-    can! :create_container_image
-    can! :update_container_image
-    can! :create_environment
-    can! :create_deployment
-  end
-
-  def master_access!
-    can! :delete_protected_branch
-    can! :update_project_snippet
-    can! :update_environment
-    can! :update_deployment
-    can! :admin_milestone
-    can! :admin_project_snippet
-    can! :admin_project_member
-    can! :admin_note
-    can! :admin_wiki
-    can! :admin_project
-    can! :admin_commit_status
-    can! :admin_build
-    can! :admin_container_image
-    can! :admin_pipeline
-    can! :admin_pipeline_schedule
-    can! :admin_environment
-    can! :admin_deployment
-    can! :admin_pages
-    can! :read_pages
-    can! :update_pages
-  end
-
-  def public_access!
-    can! :download_code
-    can! :fork_project
-    can! :read_commit_status
-    can! :read_pipeline
-    can! :read_pipeline_schedule
-    can! :read_container_image
-    can! :build_download_code
-    can! :build_read_container_image
-    can! :read_merge_request
-  end
-
-  def owner_access!
-    guest_access!
-    reporter_access!
-    developer_access!
-    master_access!
-    can! :change_namespace
-    can! :change_visibility_level
-    can! :rename_project
-    can! :remove_project
-    can! :archive_project
-    can! :remove_fork_project
-    can! :destroy_merge_request
-    can! :destroy_issue
-    can! :remove_pages
-  end
-
-  def team_member_owner_access!
-    team_member_reporter_access!
-  end
-
-  # Push abilities on the users team role
-  def team_access!(user)
-    access = project.team.max_member_access(user.id)
-
-    return if access < Gitlab::Access::GUEST
-    guest_access!
-
-    return if access < Gitlab::Access::REPORTER
-    reporter_access!
-    team_member_reporter_access!
-
-    return if access < Gitlab::Access::DEVELOPER
-    developer_access!
-
-    return if access < Gitlab::Access::MASTER
-    master_access!
-  end
-
-  def archived_access!
-    cannot! :create_merge_request
-    cannot! :push_code
-    cannot! :delete_protected_branch
-    cannot! :update_merge_request
-    cannot! :admin_merge_request
-  end
-
-  def disabled_features!
-    repository_enabled = project.feature_available?(:repository, user)
-
-    block_issues_abilities
-
-    unless project.feature_available?(:merge_requests, user) && repository_enabled
-      cannot!(*named_abilities(:merge_request))
-    end
+  rule { guest }.enable :guest_access
+  rule { reporter }.enable :reporter_access
+  rule { developer }.enable :developer_access
+  rule { master }.enable :master_access
+
+  rule { owner | admin }.policy do
+    enable :guest_access
+    enable :reporter_access
+    enable :developer_access
+    enable :master_access
+
+    enable :change_namespace
+    enable :change_visibility_level
+    enable :rename_project
+    enable :remove_project
+    enable :archive_project
+    enable :remove_fork_project
+    enable :destroy_merge_request
+    enable :destroy_issue
+    enable :remove_pages
+  end
 
-    unless project.feature_available?(:issues, user) || project.feature_available?(:merge_requests, user)
-      cannot!(*named_abilities(:label))
-      cannot!(*named_abilities(:milestone))
-    end
+  rule { owner | reporter }.policy do
+    enable :build_download_code
+    enable :build_read_container_image
+  end
 
-    unless project.feature_available?(:snippets, user)
-      cannot!(*named_abilities(:project_snippet))
-    end
+  rule { can?(:guest_access) }.policy do
+    enable :read_project
+    enable :read_board
+    enable :read_list
+    enable :read_wiki
+    enable :read_issue
+    enable :read_label
+    enable :read_milestone
+    enable :read_project_snippet
+    enable :read_project_member
+    enable :read_note
+    enable :create_project
+    enable :create_issue
+    enable :create_note
+    enable :upload_file
+    enable :read_cycle_analytics
+    enable :read_project_snippet
+  end
 
-    unless project.feature_available?(:wiki, user) || project.has_external_wiki?
-      cannot!(*named_abilities(:wiki))
-      cannot!(:download_wiki_code)
-    end
+  rule { can?(:reporter_access) }.policy do
+    enable :download_code
+    enable :download_wiki_code
+    enable :fork_project
+    enable :create_project_snippet
+    enable :update_issue
+    enable :admin_issue
+    enable :admin_label
+    enable :admin_list
+    enable :read_commit_status
+    enable :read_build
+    enable :read_container_image
+    enable :read_pipeline
+    enable :read_pipeline_schedule
+    enable :read_environment
+    enable :read_deployment
+    enable :read_merge_request
+  end
 
-    unless project.feature_available?(:builds, user) && repository_enabled
-      cannot!(*named_abilities(:build))
-      cannot!(*named_abilities(:pipeline) - [:read_pipeline])
-      cannot!(*named_abilities(:pipeline_schedule))
-      cannot!(*named_abilities(:environment))
-      cannot!(*named_abilities(:deployment))
-    end
+  rule { (~anonymous & public_project) | internal_access }.policy do
+    enable :public_user_access
+  end
 
-    unless repository_enabled
-      cannot! :push_code
-      cannot! :delete_protected_branch
-      cannot! :download_code
-      cannot! :fork_project
-      cannot! :read_commit_status
-    end
+  rule { can?(:public_user_access) }.policy do
+    enable :guest_access
+    enable :request_access
+  end
 
-    unless project.container_registry_enabled
-      cannot!(*named_abilities(:container_image))
-    end
+  rule { owner | admin | guest | group_member }.prevent :request_access
+  rule { ~request_access_enabled }.prevent :request_access
+
+  rule { can?(:developer_access) }.policy do
+    enable :admin_merge_request
+    enable :update_merge_request
+    enable :create_commit_status
+    enable :update_commit_status
+    enable :create_build
+    enable :update_build
+    enable :create_pipeline
+    enable :update_pipeline
+    enable :create_pipeline_schedule
+    enable :update_pipeline_schedule
+    enable :create_merge_request
+    enable :create_wiki
+    enable :push_code
+    enable :resolve_note
+    enable :create_container_image
+    enable :update_container_image
+    enable :create_environment
+    enable :create_deployment
   end
 
-  def anonymous_rules
-    return unless project.public?
+  rule { can?(:master_access) }.policy do
+    enable :delete_protected_branch
+    enable :update_project_snippet
+    enable :update_environment
+    enable :update_deployment
+    enable :admin_milestone
+    enable :admin_project_snippet
+    enable :admin_project_member
+    enable :admin_note
+    enable :admin_wiki
+    enable :admin_project
+    enable :admin_commit_status
+    enable :admin_build
+    enable :admin_container_image
+    enable :admin_pipeline
+    enable :admin_pipeline_schedule
+    enable :admin_environment
+    enable :admin_deployment
+    enable :admin_pages
+    enable :read_pages
+    enable :update_pages
+  end
 
-    base_readonly_access!
+  rule { can?(:public_user_access) }.policy do
+    enable :public_access
 
-    # Allow to read builds by anonymous user if guests are allowed
-    can! :read_build if project.public_builds?
+    enable :fork_project
+    enable :build_download_code
+    enable :build_read_container_image
+  end
 
-    disabled_features!
+  rule { archived }.policy do
+    prevent :create_merge_request
+    prevent :push_code
+    prevent :delete_protected_branch
+    prevent :update_merge_request
+    prevent :admin_merge_request
   end
 
-  def block_issues_abilities
-    unless project.feature_available?(:issues, user)
-      cannot! :read_issue if project.default_issues_tracker?
-      cannot! :create_issue
-      cannot! :update_issue
-      cannot! :admin_issue
-    end
+  rule { merge_requests_disabled | repository_disabled }.policy do
+    prevent(*create_read_update_admin(:merge_request))
   end
 
-  def named_abilities(name)
-    [
-      :"read_#{name}",
-      :"create_#{name}",
-      :"update_#{name}",
-      :"admin_#{name}"
-    ]
+  rule { issues_disabled & merge_requests_disabled }.policy do
+    prevent(*create_read_update_admin(:label))
+    prevent(*create_read_update_admin(:milestone))
+  end
+
+  rule { snippets_disabled }.policy do
+    prevent(*create_read_update_admin(:project_snippet))
+  end
+
+  rule { wiki_disabled & ~has_external_wiki }.policy do
+    prevent(*create_read_update_admin(:wiki))
+    prevent(:download_wiki_code)
+  end
+
+  rule { builds_disabled | repository_disabled }.policy do
+    prevent(*create_read_update_admin(:build))
+    prevent(*(create_read_update_admin(:pipeline) - [:read_pipeline]))
+    prevent(*create_read_update_admin(:pipeline_schedule))
+    prevent(*create_read_update_admin(:environment))
+    prevent(*create_read_update_admin(:deployment))
+  end
+
+  rule { repository_disabled }.policy do
+    prevent :push_code
+    prevent :push_code_to_protected_branches
+    prevent :download_code
+    prevent :fork_project
+    prevent :read_commit_status
+  end
+
+  rule { container_registry_disabled }.policy do
+    prevent(*create_read_update_admin(:container_image))
+  end
+
+  rule { anonymous & ~public_project }.prevent_all
+  rule { public_project }.enable(:public_access)
+
+  rule { can?(:public_access) }.policy do
+    enable :read_project
+    enable :read_board
+    enable :read_list
+    enable :read_wiki
+    enable :read_label
+    enable :read_milestone
+    enable :read_project_snippet
+    enable :read_project_member
+    enable :read_merge_request
+    enable :read_note
+    enable :read_pipeline
+    enable :read_pipeline_schedule
+    enable :read_commit_status
+    enable :read_container_image
+    enable :download_code
+    enable :download_wiki_code
+    enable :read_cycle_analytics
+
+    # NOTE: may be overridden by IssuePolicy
+    enable :read_issue
+  end
+
+  rule { public_builds }.policy do
+    enable :read_build
+  end
+
+  rule { public_builds & can?(:guest_access) }.policy do
+    enable :read_pipeline
+    enable :read_pipeline_schedule
+  end
+
+  rule { issues_disabled }.policy do
+    prevent :create_issue
+    prevent :update_issue
+    prevent :admin_issue
+  end
+
+  rule { issues_disabled & default_issues_tracker }.policy do
+    prevent :read_issue
   end
 
   private
 
-  def project_group_member?(user)
+  def is_team_member?
+    return false if @user.nil?
+
+    greedy_load_subject = false
+
+    # when scoping by subject, we want to be greedy
+    # and load *all* the members with one query.
+    greedy_load_subject ||= DeclarativePolicy.preferred_scope == :subject
+
+    # in this case we're likely to have loaded #members already
+    # anyways, and #member? would fail with an error
+    greedy_load_subject ||= !@user.persisted?
+
+    if greedy_load_subject
+      project.team.members.include?(user)
+    else
+      # otherwise we just make a specific query for
+      # this particular user.
+      team_access_level >= Gitlab::Access::GUEST
+    end
+  end
+
+  def project_group_member?
+    return false if @user.nil?
+
     project.group &&
       (
-        project.group.members_with_parents.exists?(user_id: user.id) ||
-        project.group.requesters.exists?(user_id: user.id)
+        project.group.members_with_parents.exists?(user_id: @user.id) ||
+        project.group.requesters.exists?(user_id: @user.id)
       )
   end
 
-  def access_requestable?
-    project.request_access_enabled &&
-      !owner? &&
-      !user.admin? &&
-      !project.team.member?(user) &&
-      !project_group_member?(user)
-  end
-
-  # A base set of abilities for read-only users, which
-  # is then augmented as necessary for anonymous and other
-  # read-only users.
-  def base_readonly_access!
-    can! :read_project
-    can! :read_board
-    can! :read_list
-    can! :read_wiki
-    can! :read_label
-    can! :read_milestone
-    can! :read_project_snippet
-    can! :read_project_member
-    can! :read_merge_request
-    can! :read_note
-    can! :read_pipeline
-    can! :read_pipeline_schedule
-    can! :read_commit_status
-    can! :read_container_image
-    can! :download_code
-    can! :download_wiki_code
-    can! :read_cycle_analytics
+  def team_access_level
+    return -1 if @user.nil?
 
-    # NOTE: may be overridden by IssuePolicy
-    can! :read_issue
+    # NOTE: max_member_access has its own cache
+    project.team.max_member_access(@user.id)
+  end
+
+  def feature_available?(feature)
+    case project.project_feature.access_level(feature)
+    when ProjectFeature::DISABLED
+      false
+    when ProjectFeature::PRIVATE
+      guest? || admin?
+    else
+      true
+    end
+  end
+
+  def project
+    @subject
   end
 end
diff --git a/app/policies/project_snippet_policy.rb b/app/policies/project_snippet_policy.rb
index bc5c4f32f7938afe7766135ef665145db137654a..dd270643bbf09d83d24d22a711421ba7eababeb4 100644
--- a/app/policies/project_snippet_policy.rb
+++ b/app/policies/project_snippet_policy.rb
@@ -1,25 +1,45 @@
 class ProjectSnippetPolicy < BasePolicy
-  def rules
-    # We have to check both project feature visibility and a snippet visibility and take the stricter one
-    # This will be simplified - check https://gitlab.com/gitlab-org/gitlab-ce/issues/27573
-    return unless @subject.project.feature_available?(:snippets, @user)
-    return unless Ability.allowed?(@user, :read_project, @subject.project)
-
-    can! :read_project_snippet if @subject.public?
-    return unless @user
-
-    if @user && (@subject.author == @user || @user.admin?)
-      can! :read_project_snippet
-      can! :update_project_snippet
-      can! :admin_project_snippet
-    end
-
-    if @subject.internal? && !@user.external?
-      can! :read_project_snippet
-    end
-
-    if @subject.project.team.member?(@user)
-      can! :read_project_snippet
-    end
+  delegate :project
+
+  desc "Snippet is public"
+  condition(:public_snippet, scope: :subject) { @subject.public? }
+  condition(:private_snippet, scope: :subject) { @subject.private? }
+  condition(:public_project, scope: :subject) { @subject.project.public? }
+
+  condition(:is_author) { @user && @subject.author == @user }
+
+  condition(:internal, scope: :subject) { @subject.internal? }
+
+  # We have to check both project feature visibility and a snippet visibility and take the stricter one
+  # This will be simplified - check https://gitlab.com/gitlab-org/gitlab-ce/issues/27573
+  rule { ~can?(:read_project) }.policy do
+    prevent :read_project_snippet
+    prevent :update_project_snippet
+    prevent :admin_project_snippet
+  end
+
+  # we have to use this complicated prevent because the delegated project policy
+  # is overly greedy in allowing :read_project_snippet, since it doesn't have any
+  # information about the snippet. However, :read_project_snippet on the *project*
+  # is used to hide/show various snippet-related controls, so we can't just move
+  # all of the handling here.
+  rule do
+    all?(private_snippet | (internal & external_user),
+         ~project.guest,
+         ~admin,
+         ~is_author)
+  end.prevent :read_project_snippet
+
+  rule { internal & ~is_author & ~admin }.policy do
+    prevent :update_project_snippet
+    prevent :admin_project_snippet
+  end
+
+  rule { public_snippet }.enable :read_project_snippet
+
+  rule { is_author | admin }.policy do
+    enable :read_project_snippet
+    enable :update_project_snippet
+    enable :admin_project_snippet
   end
 end
diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb
index 229846e368c50f8661a79fe475fec242207f5312..0905ddd9b38e64ee3720b589f2a4fa904f7872c4 100644
--- a/app/policies/user_policy.rb
+++ b/app/policies/user_policy.rb
@@ -1,19 +1,13 @@
 class UserPolicy < BasePolicy
-  include Gitlab::CurrentSettings
+  desc "The current user is the user in question"
+  condition(:user_is_self, score: 0) { @subject == @user }
 
-  def rules
-    can! :read_user if @user || !restricted_public_level?
+  desc "This is the ghost user"
+  condition(:subject_ghost, scope: :subject, score: 0) { @subject.ghost? }
 
-    if @user
-      if @user.admin? || @subject == @user
-        can! :destroy_user
-      end
+  rule { ~restricted_public_level }.enable :read_user
+  rule { ~anonymous }.enable :read_user
 
-      cannot! :destroy_user if @subject.ghost?
-    end
-  end
-
-  def restricted_public_level?
-    current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
-  end
+  rule { user_is_self | admin }.enable :destroy_user
+  rule { subject_ghost }.prevent :destroy_user
 end
diff --git a/app/presenters/merge_request_presenter.rb b/app/presenters/merge_request_presenter.rb
index 8bf35953d29d20390c503246e64607518367e3fd..6ba1d3165e9bf2db0e2216e8b7fabcb3cea6b627 100644
--- a/app/presenters/merge_request_presenter.rb
+++ b/app/presenters/merge_request_presenter.rb
@@ -20,30 +20,25 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
 
   def cancel_merge_when_pipeline_succeeds_path
     if can_cancel_merge_when_pipeline_succeeds?(current_user)
-      cancel_merge_when_pipeline_succeeds_namespace_project_merge_request_path(
-        project.namespace,
-        project,
-        merge_request)
+      cancel_merge_when_pipeline_succeeds_project_merge_request_path(project, merge_request)
     end
   end
 
   def create_issue_to_resolve_discussions_path
     if can?(current_user, :create_issue, project) && project.issues_enabled?
-      new_namespace_project_issue_path(project.namespace,
-                                       project,
-                                       merge_request_to_resolve_discussions_of: iid)
+      new_project_issue_path(project, merge_request_to_resolve_discussions_of: iid)
     end
   end
 
   def remove_wip_path
     if can?(current_user, :update_merge_request, merge_request.project)
-      remove_wip_namespace_project_merge_request_path(project.namespace, project, merge_request)
+      remove_wip_project_merge_request_path(project, merge_request)
     end
   end
 
   def merge_path
     if can_be_merged_by?(current_user)
-      merge_namespace_project_merge_request_path(project.namespace, project, merge_request)
+      merge_project_merge_request_path(project, merge_request)
     end
   end
 
@@ -55,7 +50,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
         notice_now: edit_in_new_fork_notice_now
       }
 
-      namespace_project_forks_path(merge_request.project.namespace, merge_request.project,
+      project_forks_path(merge_request.project,
                                    namespace_key: current_user.namespace.id,
                                    continue: continue_params)
     end
@@ -69,7 +64,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
         notice_now: edit_in_new_fork_notice_now
       }
 
-      namespace_project_forks_path(project.namespace, project,
+      project_forks_path(project,
                                    namespace_key: current_user.namespace.id,
                                    continue: continue_params)
     end
@@ -77,19 +72,19 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
 
   def conflict_resolution_path
     if conflicts.can_be_resolved_in_ui? && conflicts.can_be_resolved_by?(current_user)
-      conflicts_namespace_project_merge_request_path(project.namespace, project, merge_request)
+      conflicts_project_merge_request_path(project, merge_request)
     end
   end
 
   def target_branch_commits_path
     if target_branch_exists?
-      namespace_project_commits_path(project.namespace, project, target_branch)
+      project_commits_path(project, target_branch)
     end
   end
 
   def source_branch_path
     if source_branch_exists?
-      namespace_project_branch_path(source_project.namespace, source_project, source_branch)
+      project_branch_path(source_project, source_branch)
     end
   end
 
@@ -99,7 +94,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
 
     if source_branch_exists?
       namespace = link_to(namespace, project_path(source_project))
-      branch = link_to(branch, namespace_project_commits_path(source_project.namespace, source_project, source_branch))
+      branch = link_to(branch, project_commits_path(source_project, source_branch))
     end
 
     if for_fork?
@@ -136,7 +131,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
                                                     merge_request: merge_request,
                                                     closes_issues: closing_issues
                                                    ).assignable_issues
-    path = assign_related_issues_namespace_project_merge_request_path(project.namespace, project, merge_request)
+    path = assign_related_issues_project_merge_request_path(project, merge_request)
     if issues.present?
       pluralize_this_issue = issues.count > 1 ? "these issues" : "this issue"
       link_to "Assign yourself to #{pluralize_this_issue}", path, method: :post
diff --git a/app/serializers/build_action_entity.rb b/app/serializers/build_action_entity.rb
index 301b718d06000af44d91bd882015fac63268b30d..f2d76a8ad813b1f0d3854e74b407a3236f62f299 100644
--- a/app/serializers/build_action_entity.rb
+++ b/app/serializers/build_action_entity.rb
@@ -6,10 +6,7 @@ class BuildActionEntity < Grape::Entity
   end
 
   expose :path do |build|
-    play_namespace_project_job_path(
-      build.project.namespace,
-      build.project,
-      build)
+    play_project_job_path(build.project, build)
   end
 
   expose :playable?, as: :playable
diff --git a/app/serializers/build_artifact_entity.rb b/app/serializers/build_artifact_entity.rb
index cb55c98f7c6db042c27de703d6296c784b2b1934..6e0e33bc09b758d48b5ea437dd405d9c5caebe53 100644
--- a/app/serializers/build_artifact_entity.rb
+++ b/app/serializers/build_artifact_entity.rb
@@ -9,24 +9,15 @@ class BuildArtifactEntity < Grape::Entity
   expose :artifacts_expire_at, as: :expire_at
 
   expose :path do |job|
-    download_namespace_project_job_artifacts_path(
-      project.namespace,
-      project,
-      job)
+    download_project_job_artifacts_path(project, job)
   end
 
   expose :keep_path, if: -> (*) { job.has_expiring_artifacts? } do |job|
-    keep_namespace_project_job_artifacts_path(
-      project.namespace,
-      project,
-      job)
+    keep_project_job_artifacts_path(project, job)
   end
 
   expose :browse_path do |job|
-    browse_namespace_project_job_artifacts_path(
-      project.namespace,
-      project,
-      job)
+    browse_project_job_artifacts_path(project, job)
   end
 
   private
diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb
index eeb5399aa8b4becd36cf373740d041aa337c01fe..20f9938f038333d38e63c33055e3e5dd199f15f2 100644
--- a/app/serializers/build_details_entity.rb
+++ b/app/serializers/build_details_entity.rb
@@ -7,7 +7,7 @@ class BuildDetailsEntity < JobEntity
 
   expose :erased_by, if: -> (*) { build.erased? }, using: UserEntity
   expose :erase_path, if: -> (*) { build.erasable? && can?(current_user, :update_build, project) } do |build|
-    erase_namespace_project_job_path(project.namespace, project, build)
+    erase_project_job_path(project, build)
   end
 
   expose :merge_request, if: -> (*) { can?(current_user, :read_merge_request, build.merge_request) } do
@@ -16,23 +16,23 @@ class BuildDetailsEntity < JobEntity
     end
 
     expose :path do |build|
-      namespace_project_merge_request_path(project.namespace, project, build.merge_request)
+      project_merge_request_path(project, build.merge_request)
     end
   end
 
   expose :new_issue_path, if: -> (*) { can?(request.current_user, :create_issue, project) && build.failed? } do |build|
-    new_namespace_project_issue_path(project.namespace, project, issue: build_failed_issue_options)
+    new_project_issue_path(project, issue: build_failed_issue_options)
   end
 
   expose :raw_path do |build|
-    raw_namespace_project_job_path(project.namespace, project, build)
+    raw_project_job_path(project, build)
   end
 
   private
 
   def build_failed_issue_options
     { title: "Build Failed ##{build.id}",
-      description: namespace_project_job_path(project.namespace, project, build) }
+      description: project_job_path(project, build) }
   end
 
   def current_user
diff --git a/app/serializers/commit_entity.rb b/app/serializers/commit_entity.rb
index 31763955f978d606c1c90ef2d9913407fb519d4a..e4e9d8ef90a30ead66c371fadea3d25c4242507e 100644
--- a/app/serializers/commit_entity.rb
+++ b/app/serializers/commit_entity.rb
@@ -8,16 +8,10 @@ class CommitEntity < API::Entities::RepoCommit
   end
 
   expose :commit_url do |commit|
-    namespace_project_commit_url(
-      request.project.namespace,
-      request.project,
-      commit)
+    project_commit_url(request.project, commit)
   end
 
   expose :commit_path do |commit|
-    namespace_project_commit_path(
-      request.project.namespace,
-      request.project,
-      commit)
+    project_commit_path(request.project, commit)
   end
 end
diff --git a/app/serializers/deployment_entity.rb b/app/serializers/deployment_entity.rb
index e493c9162fd6a1a1a3a58915f5fde0c1d5f8a9ca..241c689bccdd6822630b198c0a1101946da9d9ca 100644
--- a/app/serializers/deployment_entity.rb
+++ b/app/serializers/deployment_entity.rb
@@ -11,10 +11,7 @@ class DeploymentEntity < Grape::Entity
     end
 
     expose :ref_path do |deployment|
-      namespace_project_tree_path(
-        deployment.project.namespace,
-        deployment.project,
-        id: deployment.ref)
+      project_tree_path(deployment.project, id: deployment.ref)
     end
   end
 
diff --git a/app/serializers/environment_entity.rb b/app/serializers/environment_entity.rb
index 4e8a3c67b21b11ec9e40fa8ac7e637b644b0edbe..dcaccc3007db7910947d06f9f71937b30bba4405 100644
--- a/app/serializers/environment_entity.rb
+++ b/app/serializers/environment_entity.rb
@@ -10,32 +10,20 @@ class EnvironmentEntity < Grape::Entity
   expose :stop_action?
 
   expose :metrics_path, if: -> (environment, _) { environment.has_metrics? } do |environment|
-    metrics_namespace_project_environment_path(
-      environment.project.namespace,
-      environment.project,
-      environment)
+    metrics_project_environment_path(environment.project, environment)
   end
 
   expose :environment_path do |environment|
-    namespace_project_environment_path(
-      environment.project.namespace,
-      environment.project,
-      environment)
+    project_environment_path(environment.project, environment)
   end
 
   expose :stop_path do |environment|
-    stop_namespace_project_environment_path(
-      environment.project.namespace,
-      environment.project,
-      environment)
+    stop_project_environment_path(environment.project, environment)
   end
 
   expose :terminal_path, if: ->(environment, _) { environment.has_terminals? } do |environment|
     can?(request.current_user, :admin_environment, environment.project) &&
-      terminal_namespace_project_environment_path(
-        environment.project.namespace,
-        environment.project,
-        environment)
+      terminal_project_environment_path(environment.project, environment)
   end
 
   expose :created_at, :updated_at
diff --git a/app/serializers/issuable_entity.rb b/app/serializers/issuable_entity.rb
index 65b204d4dd27bee4450f53147265a1dd9bb70fd0..bd5211b8e586e5cf4244c74fa4f59687b82f9efd 100644
--- a/app/serializers/issuable_entity.rb
+++ b/app/serializers/issuable_entity.rb
@@ -5,7 +5,6 @@ class IssuableEntity < Grape::Entity
   expose :description
   expose :lock_version
   expose :milestone_id
-  expose :position
   expose :state
   expose :title
   expose :updated_by_id
diff --git a/app/serializers/issue_entity.rb b/app/serializers/issue_entity.rb
index 35df95549b7daa5d65670dc6d3dcdc679496bf5d..c189a4992da83b7d3b9f43f622a2bc7ef327aba6 100644
--- a/app/serializers/issue_entity.rb
+++ b/app/serializers/issue_entity.rb
@@ -11,6 +11,6 @@ class IssueEntity < IssuableEntity
   expose :labels, using: LabelEntity
 
   expose :web_url do |issue|
-    namespace_project_issue_path(issue.project.namespace, issue.project, issue)
+    project_issue_path(issue.project, issue)
   end
 end
diff --git a/app/serializers/merge_request_entity.rb b/app/serializers/merge_request_entity.rb
index 7bb981041cc577f9b60abe8ff413eb96653ee35c..7ec2dbd0efe812dbf66851a8b2b26b9eee148388 100644
--- a/app/serializers/merge_request_entity.rb
+++ b/app/serializers/merge_request_entity.rb
@@ -99,9 +99,7 @@ class MergeRequestEntity < IssuableEntity
 
   expose :new_blob_path do |merge_request|
     if can?(current_user, :push_code, merge_request.project)
-      namespace_project_new_blob_path(merge_request.project.namespace,
-                                      merge_request.project,
-                                      merge_request.source_branch)
+      project_new_blob_path(merge_request.project, merge_request.source_branch)
     end
   end
 
@@ -134,30 +132,19 @@ class MergeRequestEntity < IssuableEntity
   end
 
   expose :email_patches_path do |merge_request|
-    namespace_project_merge_request_path(merge_request.project.namespace,
-                                         merge_request.project,
-                                         merge_request,
-                                         format: :patch)
+    project_merge_request_path(merge_request.project, merge_request, format: :patch)
   end
 
   expose :plain_diff_path do |merge_request|
-    namespace_project_merge_request_path(merge_request.project.namespace,
-                                         merge_request.project,
-                                         merge_request,
-                                         format: :diff)
+    project_merge_request_path(merge_request.project, merge_request, format: :diff)
   end
 
   expose :status_path do |merge_request|
-    namespace_project_merge_request_path(merge_request.target_project.namespace,
-                                         merge_request.target_project,
-                                         merge_request,
-                                         format: :json)
+    project_merge_request_path(merge_request.target_project, merge_request, format: :json)
   end
 
   expose :ci_environments_status_path do |merge_request|
-    ci_environments_status_namespace_project_merge_request_path(merge_request.project.namespace,
-                                                                merge_request.project,
-                                                                merge_request)
+    ci_environments_status_project_merge_request_path(merge_request.project, merge_request)
   end
 
   expose :merge_commit_message_with_description do |merge_request|
@@ -173,9 +160,7 @@ class MergeRequestEntity < IssuableEntity
   end
 
   expose :commit_change_content_path do |merge_request|
-    commit_change_content_namespace_project_merge_request_path(merge_request.project.namespace,
-                                                               merge_request.project,
-                                                               merge_request)
+    commit_change_content_project_merge_request_path(merge_request.project, merge_request)
   end
 
   private
diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb
index 6d1fd9d459f072d2333550173d4510fa51fb641f..c4f000b0ca3f4900395ea8ccba6b14355a338b09 100644
--- a/app/serializers/pipeline_entity.rb
+++ b/app/serializers/pipeline_entity.rb
@@ -10,10 +10,7 @@ class PipelineEntity < Grape::Entity
   expose :created_at, :updated_at
 
   expose :path do |pipeline|
-    namespace_project_pipeline_path(
-      pipeline.project.namespace,
-      pipeline.project,
-      pipeline)
+    project_pipeline_path(pipeline.project, pipeline)
   end
 
   expose :flags do
@@ -48,15 +45,11 @@ class PipelineEntity < Grape::Entity
   expose :commit, using: CommitEntity
 
   expose :retry_path, if: -> (*) { can_retry? }  do |pipeline|
-    retry_namespace_project_pipeline_path(pipeline.project.namespace,
-                                          pipeline.project,
-                                          pipeline.id)
+    retry_project_pipeline_path(pipeline.project, pipeline)
   end
 
   expose :cancel_path, if: -> (*) { can_cancel? } do |pipeline|
-    cancel_namespace_project_pipeline_path(pipeline.project.namespace,
-                                           pipeline.project,
-                                           pipeline.id)
+    cancel_project_pipeline_path(pipeline.project, pipeline)
   end
 
   expose :yaml_errors, if: -> (pipeline, _) { pipeline.has_yaml_errors? }
diff --git a/app/serializers/project_entity.rb b/app/serializers/project_entity.rb
index a471a7e6a882540be3d0232af0abcf89233a3eee..dc283ba3e7aa9483d2bcab7e78c42eae440fe1d1 100644
--- a/app/serializers/project_entity.rb
+++ b/app/serializers/project_entity.rb
@@ -5,7 +5,7 @@ class ProjectEntity < Grape::Entity
   expose :name
 
   expose :full_path do |project|
-    namespace_project_path(project.namespace, project)
+    project_path(project)
   end
 
   expose :full_name do |project|
diff --git a/app/serializers/runner_entity.rb b/app/serializers/runner_entity.rb
index ed7dacc2dbd6375a724e989e1067f3f753ffe019..e9999a36d8ab6c739fa8b3b1d4669330534545fc 100644
--- a/app/serializers/runner_entity.rb
+++ b/app/serializers/runner_entity.rb
@@ -5,7 +5,7 @@ class RunnerEntity < Grape::Entity
 
   expose :edit_path,
     if: -> (*) { can?(request.current_user, :admin_build, project) && runner.specific? } do |runner|
-    edit_namespace_project_runner_path(project.namespace, project, runner)
+    edit_project_runner_path(project, runner)
   end
 
   private
diff --git a/app/serializers/stage_entity.rb b/app/serializers/stage_entity.rb
index cee0089056fb4f0da7c10593ab8a91b145a2800d..4523b15152e6a41e429af52dcbcc371450ba102c 100644
--- a/app/serializers/stage_entity.rb
+++ b/app/serializers/stage_entity.rb
@@ -14,16 +14,14 @@ class StageEntity < Grape::Entity
   expose :detailed_status, as: :status, with: StatusEntity
 
   expose :path do |stage|
-    namespace_project_pipeline_path(
-      stage.pipeline.project.namespace,
+    project_pipeline_path(
       stage.pipeline.project,
       stage.pipeline,
       anchor: stage.name)
   end
 
   expose :dropdown_path do |stage|
-    stage_namespace_project_pipeline_path(
-      stage.pipeline.project.namespace,
+    stage_project_pipeline_path(
       stage.pipeline.project,
       stage.pipeline,
       stage: stage.name,
diff --git a/app/services/access_token_validation_service.rb b/app/services/access_token_validation_service.rb
index b2a543daa00ccae857a0aa41b29470b938955d44..9c00ea789ec437202334ec7a494c2f4cc410c6fe 100644
--- a/app/services/access_token_validation_service.rb
+++ b/app/services/access_token_validation_service.rb
@@ -5,10 +5,11 @@ class AccessTokenValidationService
   REVOKED = :revoked
   INSUFFICIENT_SCOPE = :insufficient_scope
 
-  attr_reader :token
+  attr_reader :token, :request
 
-  def initialize(token)
+  def initialize(token, request: nil)
     @token = token
+    @request = request
   end
 
   def validate(scopes: [])
@@ -27,12 +28,23 @@ class AccessTokenValidationService
   end
 
   # True if the token's scope contains any of the passed scopes.
-  def include_any_scope?(scopes)
-    if scopes.blank?
+  def include_any_scope?(required_scopes)
+    if required_scopes.blank?
       true
     else
-      # Check whether the token is allowed access to any of the required scopes.
-      Set.new(scopes).intersection(Set.new(token.scopes)).present?
+      # We're comparing each required_scope against all token scopes, which would
+      # take quadratic time. This consideration is irrelevant here because of the
+      # small number of records involved.
+      # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12300/#note_33689006
+      token_scopes = token.scopes.map(&:to_sym)
+
+      required_scopes.any? do |scope|
+        if scope.respond_to?(:sufficient?)
+          scope.sufficient?(token_scopes, request)
+        else
+          API::Scope.new(scope).sufficient?(token_scopes, request)
+        end
+      end
     end
   end
 end
diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb
index 418fa9afd6e78ef1722cbe0984edfb19976b3cb8..a1d67cbc2442202d4fcdec2297f2834ad5916f9a 100644
--- a/app/services/boards/issues/list_service.rb
+++ b/app/services/boards/issues/list_service.rb
@@ -3,7 +3,7 @@ module Boards
     class ListService < BaseService
       def execute
         issues = IssuesFinder.new(current_user, filter_params).execute
-        issues = without_board_labels(issues) unless movable_list?
+        issues = without_board_labels(issues) unless movable_list? || closed_list?
         issues = with_list_label(issues) if movable_list?
         issues.order_by_position_and_priority
       end
@@ -21,7 +21,15 @@ module Boards
       end
 
       def movable_list?
-        @movable_list ||= list.present? && list.movable?
+        return @movable_list if defined?(@movable_list)
+
+        @movable_list = list.present? && list.movable?
+      end
+
+      def closed_list?
+        return @closed_list if defined?(@closed_list)
+
+        @closed_list = list.present? && list.closed?
       end
 
       def filter_params
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index 769749c99252b5b5ae3dc60939793350a281bdb2..4f35255fb53df8b910b93b2619edaa7e61a5b759 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -33,7 +33,7 @@ module Ci
 
       unless pipeline.config_processor
         unless pipeline.ci_yaml_file
-          return error('Missing .gitlab-ci.yml file')
+          return error("Missing #{pipeline.ci_yaml_file_path} file")
         end
         return error(pipeline.yaml_errors, save: save_on_errors)
       end
@@ -67,8 +67,8 @@ module Ci
     def update_merge_requests_head_pipeline
       return unless pipeline.latest?
 
-      MergeRequest.where(source_project: @pipeline.project, source_branch: @pipeline.ref).
-        update_all(head_pipeline_id: @pipeline.id)
+      MergeRequest.where(source_project: @pipeline.project, source_branch: @pipeline.ref)
+        .update_all(head_pipeline_id: @pipeline.id)
     end
 
     def skip_ci?
diff --git a/app/services/ci/create_trigger_request_service.rb b/app/services/ci/create_trigger_request_service.rb
index beb27a5a5974a7b3e52be46d6f9d0dfba10cf886..cf3d4aee2bcff92681a12a24a6bb1aa8ed8383fe 100644
--- a/app/services/ci/create_trigger_request_service.rb
+++ b/app/services/ci/create_trigger_request_service.rb
@@ -3,8 +3,8 @@ module Ci
     def execute(project, trigger, ref, variables = nil)
       trigger_request = trigger.trigger_requests.create(variables: variables)
 
-      pipeline = Ci::CreatePipelineService.new(project, trigger.owner, ref: ref).
-        execute(:trigger, ignore_skip_ci: true, trigger_request: trigger_request)
+      pipeline = Ci::CreatePipelineService.new(project, trigger.owner, ref: ref)
+        .execute(:trigger, ignore_skip_ci: true, trigger_request: trigger_request)
 
       trigger_request if pipeline.persisted?
     end
diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb
index d6a4280ce4c514cd487843c8d4f173e2b2e3a456..b951e8d0c9f2fe74e6a290487102cfbe9cebd754 100644
--- a/app/services/ci/register_job_service.rb
+++ b/app/services/ci/register_job_service.rb
@@ -54,24 +54,24 @@ module Ci
     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.project_id = project_features.project_id').
-        where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0').
+        joins(:project).where(projects: { shared_runners_enabled: true, pending_delete: false })
+        .joins('LEFT JOIN project_features ON ci_builds.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.project_id=project_builds.project_id").
-        order('COALESCE(project_builds.running_builds, 0) ASC', 'ci_builds.id ASC')
+        joins("LEFT JOIN (#{running_builds_for_shared_runners.to_sql}) AS project_builds ON ci_builds.project_id=project_builds.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')
+      new_builds.where(project: runner.projects.without_deleted.with_builds_enabled).order('created_at ASC')
     end
 
     def running_builds_for_shared_runners
-      Ci::Build.running.where(runner: Ci::Runner.shared).
-        group(:project_id).select(:project_id, 'count(*) AS running_builds')
+      Ci::Build.running.where(runner: Ci::Runner.shared)
+        .group(:project_id).select(:project_id, 'count(*) AS running_builds')
     end
 
     def new_builds
diff --git a/app/services/concerns/issues/resolve_discussions.rb b/app/services/concerns/issues/resolve_discussions.rb
index 910a2a15e5d6e37556417ad3690605a5a0c7be53..7d45b4aa26a8b686546b55ecd2a5a54d332ac0c8 100644
--- a/app/services/concerns/issues/resolve_discussions.rb
+++ b/app/services/concerns/issues/resolve_discussions.rb
@@ -10,9 +10,9 @@ module Issues
     def merge_request_to_resolve_discussions_of
       return @merge_request_to_resolve_discussions_of if defined?(@merge_request_to_resolve_discussions_of)
 
-      @merge_request_to_resolve_discussions_of = MergeRequestsFinder.new(current_user, project_id: project.id).
-                                                     execute.
-                                                     find_by(iid: merge_request_to_resolve_discussions_of_iid)
+      @merge_request_to_resolve_discussions_of = MergeRequestsFinder.new(current_user, project_id: project.id)
+                                                     .execute
+                                                     .find_by(iid: merge_request_to_resolve_discussions_of_iid)
     end
 
     def discussions_to_resolve
diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb
index 46823418bb02fb7a301e91776f5ba12273cf0ef3..63b85c3de7d612ff6e3478bca2dda5b3377a0a9d 100644
--- a/app/services/create_deployment_service.rb
+++ b/app/services/create_deployment_service.rb
@@ -2,7 +2,7 @@ class CreateDeploymentService
   attr_reader :job
 
   delegate :expanded_environment_name,
-           :environment_url,
+           :variables,
            :project,
            to: :job
 
@@ -14,7 +14,8 @@ class CreateDeploymentService
     return unless executable?
 
     ActiveRecord::Base.transaction do
-      environment.external_url = environment_url if environment_url
+      environment.external_url = expanded_environment_url if
+        expanded_environment_url
       environment.fire_state_event(action)
 
       return unless environment.save
@@ -49,6 +50,17 @@ class CreateDeploymentService
     @environment_options ||= job.options&.dig(:environment) || {}
   end
 
+  def expanded_environment_url
+    return @expanded_environment_url if defined?(@expanded_environment_url)
+
+    @expanded_environment_url =
+      ExpandVariables.expand(environment_url, variables) if environment_url
+  end
+
+  def environment_url
+    environment_options[:url]
+  end
+
   def on_stop
     environment_options[:on_stop]
   end
diff --git a/app/services/delete_merged_branches_service.rb b/app/services/delete_merged_branches_service.rb
index 3b61158846629f5bf49b37f79d248f32d3bef6bc..5c9e2a16c711dac4e4da20943569dfe00c62e608 100644
--- a/app/services/delete_merged_branches_service.rb
+++ b/app/services/delete_merged_branches_service.rb
@@ -10,6 +10,8 @@ class DeleteMergedBranchesService < BaseService
     branches = branches.select { |branch| project.repository.merged_to_root_ref?(branch) }
     # Prevent deletion of branches relevant to open merge requests
     branches -= merge_request_branch_names
+    # Prevent deletion of protected branches
+    branches -= project.protected_branches.pluck(:name)
 
     branches.each do |branch|
       DeleteBranchService.new(project, current_user).execute(branch)
diff --git a/app/services/emails/base_service.rb b/app/services/emails/base_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ace498890978f5b23eee1efe37afc0391242ea55
--- /dev/null
+++ b/app/services/emails/base_service.rb
@@ -0,0 +1,8 @@
+module Emails
+  class BaseService
+    def initialize(user, opts)
+      @user = user
+      @email = opts[:email]
+    end
+  end
+end
diff --git a/app/services/emails/create_service.rb b/app/services/emails/create_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b6491ee98049f41dc5638f536acfd1588ecd6989
--- /dev/null
+++ b/app/services/emails/create_service.rb
@@ -0,0 +1,7 @@
+module Emails
+  class CreateService < ::Emails::BaseService
+    def execute
+      @user.emails.create(email: @email)
+    end
+  end
+end
diff --git a/app/services/emails/destroy_service.rb b/app/services/emails/destroy_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d586b9dfe0c89efdf577882b4a93c1eda3d0aeeb
--- /dev/null
+++ b/app/services/emails/destroy_service.rb
@@ -0,0 +1,17 @@
+module Emails
+  class DestroyService < ::Emails::BaseService
+    def execute
+      Email.find_by_email!(@email).destroy && update_secondary_emails!
+    end
+
+    private
+
+    def update_secondary_emails!
+      result = ::Users::UpdateService.new(@user).execute do |user|
+        user.update_secondary_emails!
+      end
+
+      result[:status] == 'success'
+    end
+  end
+end
diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb
index f23a9f6d57ce1dce82866ad07d6566dda326cf5e..bcca1386bedd0b21d64d85f9069d67db4624ef2f 100644
--- a/app/services/files/update_service.rb
+++ b/app/services/files/update_service.rb
@@ -28,8 +28,8 @@ module Files
     end
 
     def last_commit
-      @last_commit ||= Gitlab::Git::Commit.
-        last_for_path(@start_project.repository, @start_branch, @file_path)
+      @last_commit ||= Gitlab::Git::Commit
+        .last_for_path(@start_project.repository, @start_branch, @file_path)
     end
 
     def validate!
diff --git a/app/services/git_hooks_service.rb b/app/services/git_hooks_service.rb
index d222d1e63aa766113422ab087abfd02d0300cf9a..eab65d092996c668400843c45ca240ba09a846ad 100644
--- a/app/services/git_hooks_service.rb
+++ b/app/services/git_hooks_service.rb
@@ -3,8 +3,8 @@ class GitHooksService
 
   attr_accessor :oldrev, :newrev, :ref
 
-  def execute(user, repo_path, oldrev, newrev, ref)
-    @repo_path  = repo_path
+  def execute(user, project, oldrev, newrev, ref)
+    @project    = project
     @user       = Gitlab::GlId.gl_id(user)
     @oldrev     = oldrev
     @newrev     = newrev
@@ -26,7 +26,7 @@ class GitHooksService
   private
 
   def run_hook(name)
-    hook = Gitlab::Git::Hook.new(name, @repo_path)
+    hook = Gitlab::Git::Hook.new(name, @project)
     hook.trigger(@user, oldrev, newrev, ref)
   end
 end
diff --git a/app/services/git_operation_service.rb b/app/services/git_operation_service.rb
index ed6ea638235cb4c84f40e66383afc935d34a12ff..43636fde0be249fe4640d71e81d9706496445d55 100644
--- a/app/services/git_operation_service.rb
+++ b/app/services/git_operation_service.rb
@@ -120,7 +120,7 @@ class GitOperationService
   def with_hooks(ref, newrev, oldrev)
     GitHooksService.new.execute(
       user,
-      repository.path_to_repo,
+      repository.project,
       oldrev,
       newrev,
       ref) do |service|
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index fb1d4aed58bcf05e32faa90a795f198963440013..20d1fb29289e39de0b1071ecd9a65d10a13ad89d 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -86,8 +86,8 @@ class GitPushService < BaseService
 
     push_commits.last(PROCESS_COMMIT_LIMIT).each do |commit|
       if commit.matches_cross_reference_regex?
-        ProcessCommitWorker.
-          perform_async(project.id, current_user.id, commit.to_hash, default)
+        ProcessCommitWorker
+          .perform_async(project.id, current_user.id, commit.to_hash, default)
       end
     end
   end
diff --git a/app/services/groups/destroy_service.rb b/app/services/groups/destroy_service.rb
index 497fdb09cdc4f4f7db9a90ea791b503cb2dfd61b..80c51cb5a7296669c0f3af827200b13d68999cff 100644
--- a/app/services/groups/destroy_service.rb
+++ b/app/services/groups/destroy_service.rb
@@ -1,8 +1,7 @@
 module Groups
   class DestroyService < Groups::BaseService
     def async_execute
-      # Soft delete via paranoia gem
-      group.destroy
+      group.soft_delete_without_removing_associations
       job_id = GroupDestroyWorker.perform_async(group.id, current_user.id)
       Rails.logger.info("User #{current_user.id} scheduled a deletion of group ID #{group.id} with job ID #{job_id}")
     end
@@ -10,7 +9,7 @@ module Groups
     def execute
       group.prepare_for_destroy
 
-      group.projects.with_deleted.each do |project|
+      group.projects.each do |project|
         # Execute the destruction of the models immediately to ensure atomic cleanup.
         # Skip repository removal because we remove directory with namespace
         # that contain all these repositories
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index cd4d180824fc86113c604dec9648b20bc7bf2083..8dd0846f3bc4c07f2b47f0ee1c40cac13fadf081 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -142,10 +142,10 @@ class IssuableBaseService < BaseService
     LabelsFinder.new(current_user, project_id: @project.id).execute
   end
 
-  def merge_slash_commands_into_params!(issuable)
+  def merge_quick_actions_into_params!(issuable)
     description, command_params =
-      SlashCommands::InterpretService.new(project, current_user).
-        execute(params[:description], issuable)
+      QuickActions::InterpretService.new(project, current_user)
+        .execute(params[:description], issuable)
 
     # Avoid a description already set on an issuable to be overwritten by a nil
     params[:description] = description if params.key?(:description)
@@ -162,7 +162,7 @@ class IssuableBaseService < BaseService
   end
 
   def create(issuable)
-    merge_slash_commands_into_params!(issuable)
+    merge_quick_actions_into_params!(issuable)
     filter_params(issuable)
 
     params.delete(:state_event)
diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb
index 3cf4b82b9f2a29bdb5419ea2359d180a0d815d96..718a7ac1f2272145c5994501156bb74f26ee0619 100644
--- a/app/services/issues/create_service.rb
+++ b/app/services/issues/create_service.rb
@@ -30,8 +30,8 @@ module Issues
 
       Discussions::ResolveService.new(project, current_user,
                                       merge_request: merge_request_to_resolve_discussions_of,
-                                      follow_up_issue: issue).
-        execute(discussions_to_resolve)
+                                      follow_up_issue: issue)
+        .execute(discussions_to_resolve)
     end
 
     private
diff --git a/app/services/labels/promote_service.rb b/app/services/labels/promote_service.rb
index 76d0ba67b07af6616831f8e64f9369ed210c565f..43b539ded53745302012fde1aefeccad2cde57f7 100644
--- a/app/services/labels/promote_service.rb
+++ b/app/services/labels/promote_service.rb
@@ -26,29 +26,29 @@ module Labels
     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
+      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)
+      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)
+      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)
+      LabelPriority
+        .where(label: label_ids)
+        .update_all(label_id: new_label)
     end
 
     def update_project_labels(label_ids)
diff --git a/app/services/labels/transfer_service.rb b/app/services/labels/transfer_service.rb
index 514679ed29d02d6450a60a1194901e9ddab2cf88..d2ece354efc71bd62c88661e87b3bac6ebde47c6 100644
--- a/app/services/labels/transfer_service.rb
+++ b/app/services/labels/transfer_service.rb
@@ -41,16 +41,16 @@ module Labels
     end
 
     def group_labels_applied_to_issues
-      Label.joins(:issues).
-        where(
+      Label.joins(:issues)
+        .where(
           issues: { project_id: project.id },
           labels: { type: 'GroupLabel', group_id: old_group.id }
         )
     end
 
     def group_labels_applied_to_merge_requests
-      Label.joins(:merge_requests).
-        where(
+      Label.joins(:merge_requests)
+        .where(
           merge_requests: { target_project_id: project.id },
           labels: { type: 'GroupLabel', group_id: old_group.id }
         )
@@ -64,15 +64,15 @@ module Labels
     end
 
     def update_label_links(labels, old_label_id:, new_label_id:)
-      LabelLink.joins(:label).
-        merge(labels).
-        where(label_id: old_label_id).
-        update_all(label_id: new_label_id)
+      LabelLink.joins(:label)
+        .merge(labels)
+        .where(label_id: old_label_id)
+        .update_all(label_id: new_label_id)
     end
 
     def update_label_priorities(old_label_id:, new_label_id:)
-      LabelPriority.where(project_id: project.id, label_id: old_label_id).
-        update_all(label_id: new_label_id)
+      LabelPriority.where(project_id: project.id, label_id: old_label_id)
+        .update_all(label_id: new_label_id)
     end
   end
 end
diff --git a/app/services/members/authorized_destroy_service.rb b/app/services/members/authorized_destroy_service.rb
index f846d72498ffcb78fe7e16430486a4cbd901d789..de3a252d6c6797d64e5d87e0530c8e4565d8eba2 100644
--- a/app/services/members/authorized_destroy_service.rb
+++ b/app/services/members/authorized_destroy_service.rb
@@ -26,30 +26,30 @@ module Members
 
     def unassign_issues_and_merge_requests(member)
       if member.is_a?(GroupMember)
-        issues = Issue.unscoped.select(1).
-                 joins(:project).
-                 where('issues.id = issue_assignees.issue_id AND projects.namespace_id = ?', member.source_id)
+        issues = Issue.unscoped.select(1)
+                 .joins(:project)
+                 .where('issues.id = issue_assignees.issue_id AND projects.namespace_id = ?', member.source_id)
 
         # DELETE FROM issue_assignees WHERE user_id = X AND EXISTS (...)
-        IssueAssignee.unscoped.
-          where('user_id = :user_id AND EXISTS (:sub)', user_id: member.user_id, sub: issues).
-          delete_all
+        IssueAssignee.unscoped
+          .where('user_id = :user_id AND EXISTS (:sub)', user_id: member.user_id, sub: issues)
+          .delete_all
 
-        MergeRequestsFinder.new(user, group_id: member.source_id, assignee_id: member.user_id).
-          execute.
-          update_all(assignee_id: nil)
+        MergeRequestsFinder.new(user, group_id: member.source_id, assignee_id: member.user_id)
+          .execute
+          .update_all(assignee_id: nil)
       else
         project = member.source
 
         # SELECT 1 FROM issues WHERE issues.id = issue_assignees.issue_id AND issues.project_id = X
-        issues = Issue.unscoped.select(1).
-                 where('issues.id = issue_assignees.issue_id').
-                 where(project_id: project.id)
+        issues = Issue.unscoped.select(1)
+                 .where('issues.id = issue_assignees.issue_id')
+                 .where(project_id: project.id)
 
         # DELETE FROM issue_assignees WHERE user_id = X AND EXISTS (...)
-        IssueAssignee.unscoped.
-          where('user_id = :user_id AND EXISTS (:sub)', user_id: member.user_id, sub: issues).
-          delete_all
+        IssueAssignee.unscoped
+          .where('user_id = :user_id AND EXISTS (:sub)', user_id: member.user_id, sub: issues)
+          .delete_all
 
         project.merge_requests.opened.assigned_to(member.user).update_all(assignee_id: nil)
       end
diff --git a/app/services/merge_requests/conflicts/resolve_service.rb b/app/services/merge_requests/conflicts/resolve_service.rb
index c2c335b8461f8bbdd850e727000dae372804b1e4..6b6e231f4f9224854a354f28f36cfc8b4172fff3 100644
--- a/app/services/merge_requests/conflicts/resolve_service.rb
+++ b/app/services/merge_requests/conflicts/resolve_service.rb
@@ -27,10 +27,10 @@ module MergeRequests
             tree: merge_index.write_tree(rugged)
           }
 
-          conflicts_for_resolution.
-            project.
-            repository.
-            resolve_conflicts(current_user, merge_request.source_branch, commit_params)
+          conflicts_for_resolution
+            .project
+            .repository
+            .resolve_conflicts(current_user, merge_request.source_branch, commit_params)
         end
       end
 
diff --git a/app/services/merge_requests/get_urls_service.rb b/app/services/merge_requests/get_urls_service.rb
index f00a33969a8bb2914aaaa5776572972195ab64b7..668a1741736805d8296cd894fc5e5d3556382665 100644
--- a/app/services/merge_requests/get_urls_service.rb
+++ b/app/services/merge_requests/get_urls_service.rb
@@ -49,13 +49,13 @@ module MergeRequests
 
     def url_for_new_merge_request(branch_name)
       merge_request_params = { source_branch: branch_name }
-      url = Gitlab::Routing.url_helpers.new_namespace_project_merge_request_url(project.namespace, project, merge_request: merge_request_params)
+      url = Gitlab::Routing.url_helpers.project_new_merge_request_url(project, merge_request: merge_request_params)
       { branch_name: branch_name, url: url, new_merge_request: true }
     end
 
     def url_for_existing_merge_request(merge_request)
       target_project = merge_request.target_project
-      url = Gitlab::Routing.url_helpers.namespace_project_merge_request_url(target_project.namespace, target_project, merge_request)
+      url = Gitlab::Routing.url_helpers.project_merge_request_url(target_project, merge_request)
       { branch_name: merge_request.source_branch, url: url, new_merge_request: false }
     end
   end
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index fac3ac7a4c70d1ae08973a7181950acaf1f6ac31..bc846e07f24343800387b38d4e1069d59ca14496 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -61,8 +61,12 @@ module MergeRequests
       MergeRequests::PostMergeService.new(project, current_user).execute(merge_request)
 
       if params[:should_remove_source_branch].present? || @merge_request.force_remove_source_branch?
-        DeleteBranchService.new(@merge_request.source_project, branch_deletion_user).
-          execute(merge_request.source_branch)
+        # Verify again that the source branch can be removed, since branch may be protected,
+        # or the source branch may have been updated.
+        if @merge_request.can_remove_source_branch?(branch_deletion_user)
+          DeleteBranchService.new(@merge_request.source_project, branch_deletion_user)
+            .execute(merge_request.source_branch)
+        end
       end
     end
 
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index 81d217929d551dd1d41b9e8af0e8dd388251ccd3..e0e7c43f802a1b9b742122b4ae757eb894b60b10 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -43,9 +43,9 @@ module MergeRequests
       end
 
       filter_merge_requests(merge_requests).each do |merge_request|
-        MergeRequests::PostMergeService.
-          new(merge_request.target_project, @current_user).
-          execute(merge_request)
+        MergeRequests::PostMergeService
+          .new(merge_request.target_project, @current_user)
+          .execute(merge_request)
       end
     end
 
@@ -56,8 +56,8 @@ module MergeRequests
     # Refresh merge request diff if we push to source or target branch of merge request
     # Note: we should update merge requests from forks too
     def reload_merge_requests
-      merge_requests = @project.merge_requests.opened.
-        by_source_or_target_branch(@branch_name).to_a
+      merge_requests = @project.merge_requests.opened
+        .by_source_or_target_branch(@branch_name).to_a
 
       # Fork merge requests
       merge_requests += MergeRequest.opened
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index 5c843a258fb3b68f21bb4f81d1f77b8b438f83b7..75a65aecd1aa784539604a9d46b75cf31761ab66 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -7,7 +7,7 @@ module MergeRequests
       params.except!(:target_project_id)
       params.except!(:source_branch)
 
-      merge_from_slash_command(merge_request) if params[:merge]
+      merge_from_quick_action(merge_request) if params[:merge]
 
       if merge_request.closed_without_fork?
         params.except!(:target_branch, :force_remove_source_branch)
@@ -74,9 +74,9 @@ module MergeRequests
       end
     end
 
-    def merge_from_slash_command(merge_request)
+    def merge_from_quick_action(merge_request)
       last_diff_sha = params.delete(:merge)
-      return unless merge_request.mergeable_with_slash_command?(current_user, last_diff_sha: last_diff_sha)
+      return unless merge_request.mergeable_with_quick_action?(current_user, last_diff_sha: last_diff_sha)
 
       merge_request.update(merge_error: nil)
 
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index f3954f6f8c4004102ea2604cc1704a82190dc603..06971483992ae642aeb2bd782714c0ab1c4b3f14 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -9,11 +9,11 @@ module Notes
       # We execute commands (extracted from `params[:note]`) on the noteable
       # **before** we save the note because if the note consists of commands
       # only, there is no need be create a note!
-      slash_commands_service = SlashCommandsService.new(project, current_user)
+      quick_actions_service = QuickActionsService.new(project, current_user)
 
-      if slash_commands_service.supported?(note)
+      if quick_actions_service.supported?(note)
         options = { merge_request_diff_head_sha: merge_request_diff_head_sha }
-        content, command_params = slash_commands_service.extract_commands(note, options)
+        content, command_params = quick_actions_service.extract_commands(note, options)
 
         only_commands = content.empty?
 
@@ -30,7 +30,7 @@ module Notes
       end
 
       if command_params.present?
-        slash_commands_service.execute(command_params, note)
+        quick_actions_service.execute(command_params, note)
 
         # We must add the error after we call #save because errors are reset
         # when #save is called
diff --git a/app/services/notes/slash_commands_service.rb b/app/services/notes/quick_actions_service.rb
similarity index 84%
rename from app/services/notes/slash_commands_service.rb
rename to app/services/notes/quick_actions_service.rb
index ad1e6f6774a174c6b84d28488747d801094ccc67..a8d0cc15527694c7cf3bc1b58b4a233e08a8d568 100644
--- a/app/services/notes/slash_commands_service.rb
+++ b/app/services/notes/quick_actions_service.rb
@@ -1,5 +1,5 @@
 module Notes
-  class SlashCommandsService < BaseService
+  class QuickActionsService < BaseService
     UPDATE_SERVICES = {
       'Issue' => Issues::UpdateService,
       'MergeRequest' => MergeRequests::UpdateService
@@ -22,8 +22,8 @@ module Notes
     def extract_commands(note, options = {})
       return [note.note, {}] unless supported?(note)
 
-      SlashCommands::InterpretService.new(project, current_user, options).
-        execute(note.note, note.noteable)
+      QuickActions::InterpretService.new(project, current_user, options)
+        .execute(note.note, note.noteable)
     end
 
     def execute(command_params, note)
diff --git a/app/services/notification_recipient_service.rb b/app/services/notification_recipient_service.rb
index 8d1820bc50483e0a186310ebabf6505463d7f869..9ac561e4bd209995884d1c4bdd757254244146c3 100644
--- a/app/services/notification_recipient_service.rb
+++ b/app/services/notification_recipient_service.rb
@@ -11,7 +11,7 @@ class NotificationRecipientService
   def build_recipients(target, current_user, action:, previous_assignee: nil, skip_current_user: true)
     custom_action = build_custom_key(action, target)
 
-    recipients = target.participants(current_user)
+    recipients = participants(target, current_user)
     recipients = add_project_watchers(recipients)
     recipients = add_custom_notifications(recipients, custom_action)
     recipients = reject_mention_users(recipients)
@@ -86,12 +86,7 @@ class NotificationRecipientService
     mentioned_users = note.mentioned_users.select { |user| user.can?(ability, subject) }
 
     # Add all users participating in the thread (author, assignee, comment authors)
-    recipients =
-      if target.respond_to?(:participants)
-        target.participants(note.author)
-      else
-        mentioned_users
-      end
+    recipients = participants(target, note.author) || mentioned_users
 
     unless note.for_personal_snippet?
       # Merge project watchers
@@ -123,6 +118,14 @@ class NotificationRecipientService
 
   protected
 
+  # Ensure that if we modify this array, we aren't modifying the memoised
+  # participants on the target.
+  def participants(target, user)
+    return unless target.respond_to?(:participants)
+
+    target.participants(user).dup
+  end
+
   # Get project/group users with CUSTOM notification level
   def add_custom_notifications(recipients, action)
     user_ids = []
diff --git a/app/services/preview_markdown_service.rb b/app/services/preview_markdown_service.rb
index 10d45bbf73c2090a7f99d6520aebc996e26b780c..4ee2c1796bd734b8218e964361dabe19a0733a71 100644
--- a/app/services/preview_markdown_service.rb
+++ b/app/services/preview_markdown_service.rb
@@ -1,6 +1,6 @@
 class PreviewMarkdownService < BaseService
   def execute
-    text, commands = explain_slash_commands(params[:text])
+    text, commands = explain_quick_actions(params[:text])
     users = find_user_references(text)
 
     success(
@@ -12,11 +12,11 @@ class PreviewMarkdownService < BaseService
 
   private
 
-  def explain_slash_commands(text)
+  def explain_quick_actions(text)
     return text, [] unless %w(Issue MergeRequest).include?(commands_target_type)
 
-    slash_commands_service = SlashCommands::InterpretService.new(project, current_user)
-    slash_commands_service.explain(text, find_commands_target)
+    quick_actions_service = QuickActions::InterpretService.new(project, current_user)
+    quick_actions_service.explain(text, find_commands_target)
   end
 
   def find_user_references(text)
@@ -36,10 +36,10 @@ class PreviewMarkdownService < BaseService
   end
 
   def commands_target_type
-    params[:slash_commands_target_type]
+    params[:quick_actions_target_type]
   end
 
   def commands_target_id
-    params[:slash_commands_target_id]
+    params[:quick_actions_target_id]
   end
 end
diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb
index 015f282892117513f8999f2c0e27de40d8a46483..fc85f398935b9d1fe3aa10006297a416e9de29bf 100644
--- a/app/services/projects/autocomplete_service.rb
+++ b/app/services/projects/autocomplete_service.rb
@@ -32,7 +32,7 @@ module Projects
         issuable: noteable,
         current_user: current_user
       }
-      SlashCommands::InterpretService.command_definitions.map do |definition|
+      QuickActions::InterpretService.command_definitions.map do |definition|
         next unless definition.available?(opts)
 
         definition.to_h(opts)
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index 1c24b27a8704f0ba9ef11f6e701451902f944d2d..4bb98e5cb4ed0e1562fb804c2857812cb5e67c0d 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -12,87 +12,122 @@ module Projects
     TransferError = Class.new(StandardError)
 
     def execute(new_namespace)
-      if new_namespace.blank?
+      @new_namespace = new_namespace
+
+      if @new_namespace.blank?
         raise TransferError, 'Please select a new namespace for your project.'
       end
-      unless allowed_transfer?(current_user, project, new_namespace)
+
+      unless allowed_transfer?(current_user, project)
         raise TransferError, 'Transfer failed, please contact an admin.'
       end
-      transfer(project, new_namespace)
+
+      transfer(project)
+
+      true
     rescue Projects::TransferService::TransferError => ex
       project.reload
       project.errors.add(:new_namespace, ex.message)
       false
     end
 
-    def transfer(project, new_namespace)
-      old_namespace = project.namespace
+    private
 
-      Project.transaction do
-        old_path = project.path_with_namespace
-        old_group = project.group
-        new_path = File.join(new_namespace.try(:full_path) || '', project.path)
+    def transfer(project)
+      @old_path = project.path_with_namespace
+      @old_group = project.group
+      @new_path = File.join(@new_namespace.try(:full_path) || '', project.path)
+      @old_namespace = project.namespace
 
-        if Project.where(path: project.path, namespace_id: new_namespace.try(:id)).present?
-          raise TransferError.new("Project with same path in target namespace already exists")
-        end
+      if Project.where(path: project.path, namespace_id: @new_namespace.try(:id)).exists?
+        raise TransferError.new("Project with same path in target namespace already exists")
+      end
 
-        if project.has_container_registry_tags?
-          # we currently doesn't support renaming repository if it contains tags in container registry
-          raise TransferError.new('Project cannot be transferred, because tags are present in its container registry')
-        end
+      if project.has_container_registry_tags?
+        # We currently don't support renaming repository if it contains tags in container registry
+        raise TransferError.new('Project cannot be transferred, because tags are present in its container registry')
+      end
 
-        project.expire_caches_before_rename(old_path)
+      attempt_transfer_transaction
+    end
+
+    def attempt_transfer_transaction
+      Project.transaction do
+        project.expire_caches_before_rename(@old_path)
 
-        # Apply new namespace id and visibility level
-        project.namespace = new_namespace
-        project.visibility_level = new_namespace.visibility_level unless project.visibility_level_allowed_by_group?
-        project.save!
+        update_namespace_and_visibility(@new_namespace)
 
         # Notifications
-        project.send_move_instructions(old_path)
+        project.send_move_instructions(@old_path)
 
         # Move main repository
-        unless gitlab_shell.mv_repository(project.repository_storage_path, old_path, new_path)
+        unless move_repo_folder(@old_path, @new_path)
           raise TransferError.new('Cannot move project')
         end
 
         # Move wiki repo also if present
-        gitlab_shell.mv_repository(project.repository_storage_path, "#{old_path}.wiki", "#{new_path}.wiki")
+        move_repo_folder("#{@old_path}.wiki", "#{@new_path}.wiki")
 
         # Move missing group labels to project
-        Labels::TransferService.new(current_user, old_group, project).execute
+        Labels::TransferService.new(current_user, @old_group, project).execute
 
         # Move uploads
-        Gitlab::UploadsTransfer.new.move_project(project.path, old_namespace.full_path, new_namespace.full_path)
+        Gitlab::UploadsTransfer.new.move_project(project.path, @old_namespace.full_path, @new_namespace.full_path)
 
         # Move pages
-        Gitlab::PagesTransfer.new.move_project(project.path, old_namespace.full_path, new_namespace.full_path)
+        Gitlab::PagesTransfer.new.move_project(project.path, @old_namespace.full_path, @new_namespace.full_path)
 
-        project.old_path_with_namespace = old_path
+        project.old_path_with_namespace = @old_path
+        project.expires_full_path_cache
 
-        SystemHooksService.new.execute_hooks_for(project, :transfer)
+        execute_system_hooks
       end
-
-      refresh_permissions(old_namespace, new_namespace)
-
-      true
+    rescue Exception # rubocop:disable Lint/RescueException
+      rollback_side_effects
+      raise
+    ensure
+      refresh_permissions
     end
 
-    def allowed_transfer?(current_user, project, namespace)
-      namespace &&
+    def allowed_transfer?(current_user, project)
+      @new_namespace &&
         can?(current_user, :change_namespace, project) &&
-        namespace.id != project.namespace_id &&
-        current_user.can?(:create_projects, namespace)
+        @new_namespace.id != project.namespace_id &&
+        current_user.can?(:create_projects, @new_namespace)
     end
 
-    def refresh_permissions(old_namespace, new_namespace)
+    def update_namespace_and_visibility(to_namespace)
+      # Apply new namespace id and visibility level
+      project.namespace = to_namespace
+      project.visibility_level = to_namespace.visibility_level unless project.visibility_level_allowed_by_group?
+      project.save!
+    end
+
+    def refresh_permissions
       # This ensures we only schedule 1 job for every user that has access to
       # the namespaces.
-      user_ids = old_namespace.user_ids_for_project_authorizations |
-        new_namespace.user_ids_for_project_authorizations
+      user_ids = @old_namespace.user_ids_for_project_authorizations |
+        @new_namespace.user_ids_for_project_authorizations
 
       UserProjectAccessChangedService.new(user_ids).execute
     end
+
+    def rollback_side_effects
+      rollback_folder_move
+      update_namespace_and_visibility(@old_namespace)
+    end
+
+    def rollback_folder_move
+      move_repo_folder(@new_path, @old_path)
+      move_repo_folder("#{@new_path}.wiki", "#{@old_path}.wiki")
+    end
+
+    def move_repo_folder(from_name, to_name)
+      gitlab_shell.mv_repository(project.repository_storage_path, from_name, to_name)
+    end
+
+    def execute_system_hooks
+      SystemHooksService.new.execute_hooks_for(project, :transfer)
+    end
   end
 end
diff --git a/app/services/projects/unlink_fork_service.rb b/app/services/projects/unlink_fork_service.rb
index 315c3e162926bcf231cca493152492a5fb85eb45..f385e426827dbea71e7dabf6dbd3c6860f11537e 100644
--- a/app/services/projects/unlink_fork_service.rb
+++ b/app/services/projects/unlink_fork_service.rb
@@ -10,7 +10,7 @@ module Projects
       merge_requests = @project.forked_from_project.merge_requests.opened.from_project(@project)
 
       merge_requests.each do |mr|
-        MergeRequests::CloseService.new(@project, @current_user).execute(mr)
+        ::MergeRequests::CloseService.new(@project, @current_user).execute(mr)
       end
 
       @project.forked_project_link.destroy
diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb
index 17cf71cf098e19fb8ef5bf0f5062b0e6833b1925..e60b854f916c109284c6f16b232c49b0f58fef67 100644
--- a/app/services/projects/update_pages_service.rb
+++ b/app/services/projects/update_pages_service.rb
@@ -93,10 +93,11 @@ module Projects
       end
 
       # Requires UnZip at least 6.00 Info-ZIP.
+      # -qq be (very) quiet
       # -n  never overwrite existing files
       # We add * to end of SITE_PATH, because we want to extract SITE_PATH and all subdirectories
       site_path = File.join(SITE_PATH, '*')
-      unless system(*%W(unzip -n #{artifacts} #{site_path} -d #{temp_path}))
+      unless system(*%W(unzip -qq -n #{artifacts} #{site_path} -d #{temp_path}))
         raise 'pages failed to extract'
       end
     end
diff --git a/app/services/slash_commands/interpret_service.rb b/app/services/quick_actions/interpret_service.rb
similarity index 85%
rename from app/services/slash_commands/interpret_service.rb
rename to app/services/quick_actions/interpret_service.rb
index 83144b1e0111cd45149f8c698dbfb3fdd9fe81a0..e4dfe87e6140b81b6575d47576ab3c8799caf476 100644
--- a/app/services/slash_commands/interpret_service.rb
+++ b/app/services/quick_actions/interpret_service.rb
@@ -1,13 +1,13 @@
-module SlashCommands
+module QuickActions
   class InterpretService < BaseService
-    include Gitlab::SlashCommands::Dsl
+    include Gitlab::QuickActions::Dsl
 
     attr_reader :issuable
 
     # Takes a text and interprets the commands that are extracted from it.
     # Returns the content without commands, and hash of changes to be applied to a record.
     def execute(content, issuable)
-      return [content, {}] unless current_user.can?(:use_slash_commands)
+      return [content, {}] unless current_user.can?(:use_quick_actions)
 
       @issuable = issuable
       @updates = {}
@@ -20,7 +20,7 @@ module SlashCommands
     # Takes a text and interprets the commands that are extracted from it.
     # Returns the content without commands, and array of changes explained.
     def explain(content, issuable)
-      return [content, []] unless current_user.can?(:use_slash_commands)
+      return [content, []] unless current_user.can?(:use_quick_actions)
 
       @issuable = issuable
 
@@ -32,7 +32,7 @@ module SlashCommands
     private
 
     def extractor
-      Gitlab::SlashCommands::Extractor.new(self.class.command_definitions)
+      Gitlab::QuickActions::Extractor.new(self.class.command_definitions)
     end
 
     desc do
@@ -71,7 +71,7 @@ module SlashCommands
       last_diff_sha = params && params[:merge_request_diff_head_sha]
       issuable.is_a?(MergeRequest) &&
         issuable.persisted? &&
-        issuable.mergeable_with_slash_command?(current_user, autocomplete_precheck: !last_diff_sha, last_diff_sha: last_diff_sha)
+        issuable.mergeable_with_quick_action?(current_user, autocomplete_precheck: !last_diff_sha, last_diff_sha: last_diff_sha)
     end
     command :merge do
       @updates[:merge] = params[:merge_request_diff_head_sha]
@@ -92,9 +92,12 @@ module SlashCommands
 
     desc 'Assign'
     explanation do |users|
-      "Assigns #{users.first.to_reference}." if users.any?
+      users = issuable.allows_multiple_assignees? ? users : users.take(1)
+      "Assigns #{users.map(&:to_reference).to_sentence}."
+    end
+    params do
+      issuable.allows_multiple_assignees? ? '@user1 @user2' : '@user'
     end
-    params '@user'
     condition do
       current_user.can?(:"admin_#{issuable.to_ability_name}", project)
     end
@@ -104,28 +107,69 @@ module SlashCommands
     command :assign do |users|
       next if users.empty?
 
-      if issuable.is_a?(Issue)
-        @updates[:assignee_ids] = [users.last.id]
+      @updates[:assignee_ids] =
+        if issuable.allows_multiple_assignees?
+          issuable.assignees.pluck(:id) + users.map(&:id)
+        else
+          [users.last.id]
+        end
+    end
+
+    desc do
+      if issuable.allows_multiple_assignees?
+        'Remove all or specific assignee(s)'
       else
-        @updates[:assignee_id] = users.last.id
+        'Remove assignee'
       end
     end
-
-    desc 'Remove assignee'
     explanation do
-      "Removes assignee #{issuable.assignees.first.to_reference}."
+      "Removes #{'assignee'.pluralize(issuable.assignees.size)} #{issuable.assignees.map(&:to_reference).to_sentence}."
+    end
+    params do
+      issuable.allows_multiple_assignees? ? '@user1 @user2' : ''
     end
     condition do
       issuable.persisted? &&
         issuable.assignees.any? &&
         current_user.can?(:"admin_#{issuable.to_ability_name}", project)
     end
-    command :unassign do
-      if issuable.is_a?(Issue)
-        @updates[:assignee_ids] = []
-      else
-        @updates[:assignee_id] = nil
-      end
+    parse_params do |unassign_param|
+      # When multiple users are assigned, all will be unassigned if multiple assignees are no longer allowed
+      extract_users(unassign_param) if issuable.allows_multiple_assignees?
+    end
+    command :unassign do |users = nil|
+      @updates[:assignee_ids] =
+        if users&.any?
+          issuable.assignees.pluck(:id) - users.map(&:id)
+        else
+          []
+        end
+    end
+
+    desc do
+      "Change assignee#{'(s)' if issuable.allows_multiple_assignees?}"
+    end
+    explanation do |users|
+      users = issuable.allows_multiple_assignees? ? users : users.take(1)
+      "Change #{'assignee'.pluralize(users.size)} to #{users.map(&:to_reference).to_sentence}."
+    end
+    params do
+      issuable.allows_multiple_assignees? ? '@user1 @user2' : '@user'
+    end
+    condition do
+      issuable.persisted? &&
+        current_user.can?(:"admin_#{issuable.to_ability_name}", project)
+    end
+    parse_params do |assignee_param|
+      extract_users(assignee_param)
+    end
+    command :reassign do |users|
+      @updates[:assignee_ids] =
+        if issuable.allows_multiple_assignees?
+          users.map(&:id)
+        else
+          [users.last.id]
+        end
     end
 
     desc 'Set milestone'
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 0837c07e6aa8c92f92998d16abc75db77f256a9a..da0f21d449ab6909bbb94118a8aa6aa0c6f7410b 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -282,7 +282,7 @@ module SystemNoteService
     body = "changed this line in"
     if version_params = merge_request.version_params_for(diff_refs)
       line_code = change_position.line_code(project.repository)
-      url = url_helpers.diffs_namespace_project_merge_request_url(project.namespace, project, merge_request, version_params.merge(anchor: line_code))
+      url = url_helpers.diffs_project_merge_request_url(project, merge_request, version_params.merge(anchor: line_code))
 
       body << " [version #{version_index} of the diff](#{url})"
     else
@@ -413,7 +413,7 @@ module SystemNoteService
   #
   #   "created branch `201-issue-branch-button`"
   def new_issue_branch(issue, project, author, branch)
-    link = url_helpers.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch)
+    link = url_helpers.project_compare_url(project, from: project.default_branch, to: branch)
 
     body = "created branch [`#{branch}`](#{link})"
 
@@ -630,10 +630,9 @@ module SystemNoteService
   def diff_comparison_url(merge_request, project, oldrev)
     diff_id = merge_request.merge_request_diff.id
 
-    url_helpers.diffs_namespace_project_merge_request_url(
-      project.namespace,
+    url_helpers.diffs_project_merge_request_url(
       project,
-      merge_request.iid,
+      merge_request,
       diff_id: diff_id,
       start_sha: oldrev
     )
diff --git a/app/services/tags/create_service.rb b/app/services/tags/create_service.rb
index 1756da9e519a993b6d9b5bf95a11f0c425b84be3..674792f61389b85f63186cafe5f5164958d83834 100644
--- a/app/services/tags/create_service.rb
+++ b/app/services/tags/create_service.rb
@@ -19,8 +19,8 @@ module Tags
 
       if new_tag
         if release_description
-          CreateReleaseService.new(@project, @current_user).
-            execute(tag_name, release_description)
+          CreateReleaseService.new(@project, @current_user)
+            .execute(tag_name, release_description)
         end
 
         success.merge(tag: new_tag)
diff --git a/app/services/users/build_service.rb b/app/services/users/build_service.rb
index 363135ef09bbf8d5fd384cabf1b5f605b3d24c0e..ff234a3440fa84d1a97ec9fff54ce8ab9476f049 100644
--- a/app/services/users/build_service.rb
+++ b/app/services/users/build_service.rb
@@ -1,5 +1,4 @@
 module Users
-  # Service for building a new user.
   class BuildService < BaseService
     def initialize(current_user, params = {})
       @current_user = current_user
diff --git a/app/services/users/create_service.rb b/app/services/users/create_service.rb
index e22f7225ae2dab6aa37df903c2e54367957794af..74abc017cea0d24cd62c7f59ad4ace3f9cd75e92 100644
--- a/app/services/users/create_service.rb
+++ b/app/services/users/create_service.rb
@@ -1,5 +1,4 @@
 module Users
-  # Service for creating a new user.
   class CreateService < BaseService
     def initialize(current_user, params = {})
       @current_user = current_user
diff --git a/app/services/users/destroy_service.rb b/app/services/users/destroy_service.rb
index 673afb8b5b9502e18ce5fb3523d258027b9cb319..9d7237c2fbb6c32c227c3aad3ff123f2f386c40a 100644
--- a/app/services/users/destroy_service.rb
+++ b/app/services/users/destroy_service.rb
@@ -35,7 +35,7 @@ module Users
         Groups::DestroyService.new(group, current_user).execute
       end
 
-      user.personal_projects.with_deleted.each do |project|
+      user.personal_projects.each do |project|
         # Skip repository removal because we remove directory with namespace
         # that contain all this repositories
         ::Projects::DestroyService.new(project, current_user, skip_repo: true).execute
diff --git a/app/services/users/refresh_authorized_projects_service.rb b/app/services/users/refresh_authorized_projects_service.rb
index 3e07b8110277d0c0118da081ca21bf1ba0ec4661..f028f5eb0d460d881277d51a7a01c3ffb353da2c 100644
--- a/app/services/users/refresh_authorized_projects_service.rb
+++ b/app/services/users/refresh_authorized_projects_service.rb
@@ -34,7 +34,7 @@ module Users
         # Keep trying until we obtain the lease. If we don't do so we may end up
         # not updating the list of authorized projects properly. To prevent
         # hammering Redis too much we'll wait for a bit between retries.
-        sleep(1)
+        sleep(0.1)
       end
 
       begin
diff --git a/app/services/users/update_service.rb b/app/services/users/update_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..dfbd6016c3fc95e38cd5bbacca11e3b52e74fb9c
--- /dev/null
+++ b/app/services/users/update_service.rb
@@ -0,0 +1,34 @@
+module Users
+  class UpdateService < BaseService
+    def initialize(user, params = {})
+      @user = user
+      @params = params.dup
+    end
+
+    def execute(validate: true, &block)
+      yield(@user) if block_given?
+
+      assign_attributes(&block)
+
+      if @user.save(validate: validate)
+        success
+      else
+        error(@user.errors.full_messages.uniq.join('. '))
+      end
+    end
+
+    def execute!(*args, &block)
+      result = execute(*args, &block)
+
+      raise ActiveRecord::RecordInvalid.new(@user) unless result[:status] == :success
+
+      true
+    end
+
+    private
+
+    def assign_attributes(&block)
+      @user.assign_attributes(params) if params.any?
+    end
+  end
+end
diff --git a/app/validators/dynamic_path_validator.rb b/app/validators/dynamic_path_validator.rb
index 27ac60637fd402d6879784be198aad1540f9f15d..4688aabc2a8656ed8ab39f0731db56e9ecfb912b 100644
--- a/app/validators/dynamic_path_validator.rb
+++ b/app/validators/dynamic_path_validator.rb
@@ -26,7 +26,7 @@ class DynamicPathValidator < ActiveModel::EachValidator
   end
 
   def path_valid_for_record?(record, value)
-    full_path = record.respond_to?(:full_path) ? record.full_path : value
+    full_path = record.respond_to?(:build_full_path) ? record.build_full_path : value
 
     return true unless full_path
 
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 95dffdafabe27e431edc5e3703c487719382a47f..5f5eeb8b9a9c3cada53d9199c8f03fb0ff588c81 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -22,7 +22,9 @@
     .form-group
       = f.label :restricted_visibility_levels, class: 'control-label col-sm-2'
       .col-sm-10
-        - restricted_level_checkboxes('restricted-visibility-help').each do |level|
+        - checkbox_name = 'application_setting[restricted_visibility_levels][]'
+        = hidden_field_tag(checkbox_name)
+        - restricted_level_checkboxes('restricted-visibility-help', checkbox_name).each do |level|
           .checkbox
             = level
         %span.help-block#restricted-visibility-help
@@ -325,6 +327,10 @@
           = f.label :prometheus_metrics_enabled do
             = f.check_box :prometheus_metrics_enabled
             Enable Prometheus Metrics
+            - unless Gitlab::Metrics.metrics_folder_present?
+              .help-block
+                %strong.cred WARNING:
+                Environment variable `prometheus_multiproc_dir` does not exist or is not pointing to a valid directory.
 
   %fieldset
     %legend Background Jobs
diff --git a/app/views/admin/broadcast_messages/_form.html.haml b/app/views/admin/broadcast_messages/_form.html.haml
index 2269fb1fd8c8c2eec344d9c2c4f10de68f6ef9d0..5a4ed1c3a2a02049167321f7a8ebf3e70022009e 100644
--- a/app/views/admin/broadcast_messages/_form.html.haml
+++ b/app/views/admin/broadcast_messages/_form.html.haml
@@ -21,11 +21,11 @@
   .form-group.js-toggle-colors-container.hide
     = f.label :color, "Background Color", class: 'control-label'
     .col-sm-10
-      = f.text_field :color, class: "form-control"
+      = f.color_field :color, class: "form-control"
   .form-group.js-toggle-colors-container.hide
     = f.label :font, "Font Color", class: 'control-label'
     .col-sm-10
-      = f.text_field :font, class: "form-control"
+      = f.color_field :font, class: "form-control"
   .form-group
     = f.label :starts_at, class: 'control-label'
     .col-sm-10.datetime-controls
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 3c9f932a225270415ecd0ddbf30f7adf31e13d9b..128b5dc01aba95b96a38efca15e834a8ece5e674 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -5,182 +5,182 @@
   .admin-dashboard.prepend-top-default
     .row
       .col-md-4
-        %h4 Statistics
-        %hr
-        %p
-          Forks
-          %span.light.pull-right
-            = number_with_delimiter(ForkedProjectLink.count)
-        %p
-          Issues
-          %span.light.pull-right
-            = number_with_delimiter(Issue.count)
-        %p
-          Merge Requests
-          %span.light.pull-right
-            = number_with_delimiter(MergeRequest.count)
-        %p
-          Notes
-          %span.light.pull-right
-            = number_with_delimiter(Note.count)
-        %p
-          Snippets
-          %span.light.pull-right
-            = number_with_delimiter(Snippet.count)
-        %p
-          SSH Keys
-          %span.light.pull-right
-            = number_with_delimiter(Key.count)
-        %p
-          Milestones
-          %span.light.pull-right
-            = number_with_delimiter(Milestone.count)
-        %p
-          Active Users
-          %span.light.pull-right
-            = number_with_delimiter(User.active.count)
+        .info-well
+          .well-segment.admin-well
+            %h4 Statistics
+            %p
+              Forks
+              %span.light.pull-right
+                = number_with_delimiter(ForkedProjectLink.count)
+            %p
+              Issues
+              %span.light.pull-right
+                = number_with_delimiter(Issue.count)
+            %p
+              Merge Requests
+              %span.light.pull-right
+                = number_with_delimiter(MergeRequest.count)
+            %p
+              Notes
+              %span.light.pull-right
+                = number_with_delimiter(Note.count)
+            %p
+              Snippets
+              %span.light.pull-right
+                = number_with_delimiter(Snippet.count)
+            %p
+              SSH Keys
+              %span.light.pull-right
+                = number_with_delimiter(Key.count)
+            %p
+              Milestones
+              %span.light.pull-right
+                = number_with_delimiter(Milestone.count)
+            %p
+              Active Users
+              %span.light.pull-right
+                = number_with_delimiter(User.active.count)
       .col-md-4
-        %h4
-          Features
-        %hr
-        - sign_up = "Sign up"
-        %p{ "aria-label" => "#{sign_up}: status " + (signup_enabled? ? "on" : "off") }
-          = sign_up
-          %span.light.pull-right
-            = boolean_to_icon signup_enabled?
-        - ldap = "LDAP"
-        %p{ "aria-label" => "#{ldap}: status " + (Gitlab.config.ldap.enabled ? "on" : "off") }
-          = ldap
-          %span.light.pull-right
-            = boolean_to_icon Gitlab.config.ldap.enabled
-        - gravatar = "Gravatar"
-        %p{ "aria-label" => "#{gravatar}: status " + (gravatar_enabled? ? "on" : "off") }
-          = gravatar
-          %span.light.pull-right
-            = boolean_to_icon gravatar_enabled?
-        - omniauth = "OmniAuth"
-        %p{ "aria-label" => "#{omniauth}: status " + (Gitlab.config.omniauth.enabled ? "on" : "off") }
-          = omniauth
-          %span.light.pull-right
-            = boolean_to_icon Gitlab.config.omniauth.enabled
-        - reply_email = "Reply by email"
-        %p{ "aria-label" => "#{reply_email}: status " + (Gitlab::IncomingEmail.enabled? ? "on" : "off") }
-          = reply_email
-          %span.light.pull-right
-            = boolean_to_icon Gitlab::IncomingEmail.enabled?
-        - container_reg = "Container Registry"
-        %p{ "aria-label" => "#{container_reg}: status " + (Gitlab.config.registry.enabled ? "on" : "off") }
-          = container_reg
-          %span.light.pull-right
-            = boolean_to_icon Gitlab.config.registry.enabled
-        - gitlab_pages = 'GitLab Pages'
-        - gitlab_pages_enabled = Gitlab.config.pages.enabled
-        %p{ "aria-label" => "#{gitlab_pages}: status " + (gitlab_pages_enabled ? "on" : "off") }
-          = gitlab_pages
-          %span.light.pull-right
-            = boolean_to_icon gitlab_pages_enabled
-        - gitlab_shared_runners = 'Shared Runners'
-        - gitlab_shared_runners_enabled = Gitlab.config.gitlab_ci.shared_runners_enabled
-        %p{ "aria-label" => "#{gitlab_shared_runners}: status " + (gitlab_shared_runners_enabled ? "on" : "off") }
-          = gitlab_shared_runners
-          %span.light.pull-right
-            = boolean_to_icon gitlab_shared_runners_enabled
-
+        .info-well
+          .well-segment.admin-well
+            %h4 Features
+            - sign_up = "Sign up"
+            %p{ "aria-label" => "#{sign_up}: status " + (signup_enabled? ? "on" : "off") }
+              = sign_up
+              %span.light.pull-right
+                = boolean_to_icon signup_enabled?
+            - ldap = "LDAP"
+            %p{ "aria-label" => "#{ldap}: status " + (Gitlab.config.ldap.enabled ? "on" : "off") }
+              = ldap
+              %span.light.pull-right
+                = boolean_to_icon Gitlab.config.ldap.enabled
+            - gravatar = "Gravatar"
+            %p{ "aria-label" => "#{gravatar}: status " + (gravatar_enabled? ? "on" : "off") }
+              = gravatar
+              %span.light.pull-right
+                = boolean_to_icon gravatar_enabled?
+            - omniauth = "OmniAuth"
+            %p{ "aria-label" => "#{omniauth}: status " + (Gitlab.config.omniauth.enabled ? "on" : "off") }
+              = omniauth
+              %span.light.pull-right
+                = boolean_to_icon Gitlab.config.omniauth.enabled
+            - reply_email = "Reply by email"
+            %p{ "aria-label" => "#{reply_email}: status " + (Gitlab::IncomingEmail.enabled? ? "on" : "off") }
+              = reply_email
+              %span.light.pull-right
+                = boolean_to_icon Gitlab::IncomingEmail.enabled?
+            - container_reg = "Container Registry"
+            %p{ "aria-label" => "#{container_reg}: status " + (Gitlab.config.registry.enabled ? "on" : "off") }
+              = container_reg
+              %span.light.pull-right
+                = boolean_to_icon Gitlab.config.registry.enabled
+            - gitlab_pages = 'GitLab Pages'
+            - gitlab_pages_enabled = Gitlab.config.pages.enabled
+            %p{ "aria-label" => "#{gitlab_pages}: status " + (gitlab_pages_enabled ? "on" : "off") }
+              = gitlab_pages
+              %span.light.pull-right
+                = boolean_to_icon gitlab_pages_enabled
+            - gitlab_shared_runners = 'Shared Runners'
+            - gitlab_shared_runners_enabled = Gitlab.config.gitlab_ci.shared_runners_enabled
+            %p{ "aria-label" => "#{gitlab_shared_runners}: status " + (gitlab_shared_runners_enabled ? "on" : "off") }
+              = gitlab_shared_runners
+              %span.light.pull-right
+                = boolean_to_icon gitlab_shared_runners_enabled
       .col-md-4
-        %h4
-          Components
-          - if current_application_settings.version_check_enabled
-            .pull-right
-              = version_status_badge
-
-        %hr
-        %p
-          GitLab
-          %span.pull-right
-            = Gitlab::VERSION
-        %p
-          GitLab Shell
-          %span.pull-right
-            = Gitlab::Shell.new.version
-        %p
-          GitLab Workhorse
-          %span.pull-right
-            = gitlab_workhorse_version
-        %p
-          GitLab API
-          %span.pull-right
-            = API::API::version
-        %p
-          Git
-          %span.pull-right
-            = Gitlab::Git.version
-        %p
-          Ruby
-          %span.pull-right
-            #{RUBY_VERSION}p#{RUBY_PATCHLEVEL}
-
-        %p
-          Rails
-          %span.pull-right
-            #{Rails::VERSION::STRING}
-
-        %p
-          = Gitlab::Database.adapter_name
-          %span.pull-right
-            = Gitlab::Database.version
-    %hr
+        .info-well
+          .well-segment.admin-well
+            %h4
+              Components
+              - if current_application_settings.version_check_enabled
+                .pull-right
+                  = version_status_badge
+            %p
+              GitLab
+              %span.pull-right
+                = Gitlab::VERSION
+            %p
+              GitLab Shell
+              %span.pull-right
+                = Gitlab::Shell.new.version
+            %p
+              GitLab Workhorse
+              %span.pull-right
+                = gitlab_workhorse_version
+            %p
+              GitLab API
+              %span.pull-right
+                = API::API::version
+            %p
+              Git
+              %span.pull-right
+                = Gitlab::Git.version
+            %p
+              Ruby
+              %span.pull-right
+                #{RUBY_VERSION}p#{RUBY_PATCHLEVEL}
+            %p
+              Rails
+              %span.pull-right
+                #{Rails::VERSION::STRING}
+            %p
+              = Gitlab::Database.adapter_name
+              %span.pull-right
+                = Gitlab::Database.version
     .row
       .col-sm-4
-        .light-well.well-centered
-          %h4 Projects
-          .data
+        .info-well.dark-well
+          .well-segment.well-centered
             = link_to admin_projects_path do
-              %h1= number_with_delimiter(Project.cached_count)
+              %h3.text-center
+                Projects:
+                = number_with_delimiter(Project.cached_count)
             %hr
             = link_to('New project', new_project_path, class: "btn btn-new")
       .col-sm-4
-        .light-well.well-centered
-          %h4 Users
-          .data
+        .info-well.dark-well
+          .well-segment.well-centered
             = link_to admin_users_path do
-              %h1= number_with_delimiter(User.count)
+              %h3.text-center
+                Users:
+                = number_with_delimiter(User.count)
             %hr
             = link_to 'New user', new_admin_user_path, class: "btn btn-new"
       .col-sm-4
-        .light-well.well-centered
-          %h4 Groups
-          .data
+        .info-well.dark-well
+          .well-segment.well-centered
             = link_to admin_groups_path do
-              %h1= number_with_delimiter(Group.count)
+              %h3.text-center
+                Groups
+                = number_with_delimiter(Group.count)
             %hr
             = link_to 'New group', new_admin_group_path, class: "btn btn-new"
-
-    .row.prepend-top-10
+    .row
       .col-md-4
-        %h4 Latest projects
-        %hr
-        - @projects.each do |project|
-          %p
-            = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project], class: 'str-truncated-60'
-            %span.light.pull-right
-              #{time_ago_with_tooltip(project.created_at)}
-
+        .info-well
+          .well-segment.admin-well
+            %h4 Latest projects
+            - @projects.each do |project|
+              %p
+                = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project], class: 'str-truncated-60'
+                %span.light.pull-right
+                  #{time_ago_with_tooltip(project.created_at)}
       .col-md-4
-        %h4 Latest users
-        %hr
-        - @users.each do |user|
-          %p
-            = link_to [:admin, user], class: 'str-truncated-60' do
-              = user.name
-            %span.light.pull-right
-              #{time_ago_with_tooltip(user.created_at)}
-
+        .info-well
+          .well-segment.admin-well
+            %h4 Latest users
+            - @users.each do |user|
+              %p
+                = link_to [:admin, user], class: 'str-truncated-60' do
+                  = user.name
+                %span.light.pull-right
+                  #{time_ago_with_tooltip(user.created_at)}
       .col-md-4
-        %h4 Latest groups
-        %hr
-        - @groups.each do |group|
-          %p
-            = link_to [:admin, group], class: 'str-truncated-60' do
-              = group.full_name
-            %span.light.pull-right
-              #{time_ago_with_tooltip(group.created_at)}
+        .info-well
+          .well-segment.admin-well
+            %h4 Latest groups
+            - @groups.each do |group|
+              %p
+                = link_to [:admin, group], class: 'str-truncated-60' do
+                  = group.full_name
+                %span.light.pull-right
+                  #{time_ago_with_tooltip(group.created_at)}
diff --git a/app/views/admin/projects/_projects.html.haml b/app/views/admin/projects/_projects.html.haml
index 596f367a00d8ce910da4e884729c6debb7c14f17..c69c27611893d2c2b9aef822acb3ff09672fab8a 100644
--- a/app/views/admin/projects/_projects.html.haml
+++ b/app/views/admin/projects/_projects.html.haml
@@ -4,7 +4,7 @@
       - @projects.each_with_index do |project|
         %li.project-row{ class: ('no-description' if project.description.blank?) }
           .controls
-            = link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn"
+            = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn"
             = link_to 'Delete', [project.namespace.becomes(Namespace), project], data: { confirm: remove_project_message(project) }, method: :delete, class: "btn btn-remove"
           .stats
             %span.badge
diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml
index 08a8f62711371168ded13b9e83cd4195687e3f3d..fb9057b2db5d56d17a6bfeb70a7b4a1862b65e2b 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -108,7 +108,7 @@
       .panel-heading
         Transfer project
       .panel-body
-        = form_for @project, url: transfer_admin_namespace_project_path(@project.namespace, @project), method: :put, html: { class: 'form-horizontal' } do |f|
+        = form_for @project, url: transfer_admin_project_path(@project), method: :put, html: { class: 'form-horizontal' } do |f|
           .form-group
             = f.label :new_namespace_id, "Namespace", class: 'control-label'
             .col-sm-10
@@ -128,7 +128,7 @@
       .panel-heading
         Repository check
       .panel-body
-        = form_for @project, url: repository_check_admin_namespace_project_path(@project.namespace, @project), method: :post do |f|
+        = form_for @project, url: repository_check_admin_project_path(@project), method: :post do |f|
           .form-group
             - if @project.last_repository_check_at.nil?
               This repository has never been checked.
diff --git a/app/views/admin/runners/_runner.html.haml b/app/views/admin/runners/_runner.html.haml
index d4d166ab7b63c72230bb96ac488181c124f32ab1..140688b52d312ede0ae5c003689806c7916befaa 100644
--- a/app/views/admin/runners/_runner.html.haml
+++ b/app/views/admin/runners/_runner.html.haml
@@ -32,13 +32,16 @@
       #{time_ago_in_words(runner.contacted_at)} ago
     - else
       Never
-  %td
-    .pull-right
-      = link_to 'Edit', admin_runner_path(runner), class: 'btn btn-sm'
+  %td.admin-runner-btn-group-cell
+    .pull-right.btn-group
+      = link_to admin_runner_path(runner), class: 'btn btn-sm btn-default has-tooltip', title: 'Edit', ref: 'tooltip', aria: { label: 'Edit' }, data: { placement: 'top', container: 'body'} do
+        = icon('pencil')
       &nbsp;
       - if runner.active?
-        = link_to 'Pause', [:pause, :admin, runner], data: { confirm: "Are you sure?" }, method: :get, class: 'btn btn-danger btn-sm'
+        = link_to [:pause, :admin, runner], method: :get, class: 'btn btn-sm btn-default has-tooltip', title: 'Pause', ref: 'tooltip', aria: { label: 'Pause' }, data: { placement: 'top', container: 'body', confirm: "Are you sure?" } do
+          = icon('pause')
       - else
-        = link_to 'Resume', [:resume, :admin, runner], method: :get, class: 'btn btn-success btn-sm'
-      = link_to 'Remove', [:admin, runner], data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
-
+        = link_to [:resume, :admin, runner], method: :get, class: 'btn btn-default btn-sm has-tooltip', title: 'Resume', ref: 'tooltip', aria: { label: 'Resume' }, data: { placement: 'top', container: 'body'} do
+          = icon('play')
+      = link_to [:admin, runner], method: :delete, class: 'btn btn-danger btn-sm has-tooltip', title: 'Remove', ref: 'tooltip', aria: { label: 'Remove' }, data: { placement: 'top', container: 'body', confirm: "Are you sure?" } do
+        = icon('remove')
diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml
index e242e851b4d0a94690fbaf019c8ba4834434f9d9..2da8f615470f7a441a7b63b9593c342983324ab6 100644
--- a/app/views/admin/runners/index.html.haml
+++ b/app/views/admin/runners/index.html.haml
@@ -58,20 +58,23 @@
 
   %br
 
-  .table-holder
-    %table.table
-      %thead
-        %tr
-          %th Type
-          %th Runner token
-          %th Description
-          %th Version
-          %th Projects
-          %th Jobs
-          %th Tags
-          %th Last contact
-          %th
+  - if @runners.any?
+    .table-holder
+      %table.table
+        %thead
+          %tr
+            %th Type
+            %th Runner token
+            %th Description
+            %th Version
+            %th Projects
+            %th Jobs
+            %th Tags
+            %th Last contact
+            %th
 
-      - @runners.each do |runner|
-        = render "admin/runners/runner", runner: runner
-  = paginate @runners, theme: "gitlab"
+        - @runners.each do |runner|
+          = render "admin/runners/runner", runner: runner
+    = paginate @runners, theme: "gitlab"
+  - else
+    .nothing-here-block No runners found
diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml
index 801430e525e73ee5491e47d0e9292ad1941fba03..df2bf27be9d1d9a12fa160592a5be9372f5be69a 100644
--- a/app/views/admin/runners/show.html.haml
+++ b/app/views/admin/runners/show.html.haml
@@ -85,7 +85,7 @@
         %tr.build
           %td.id
             - if project
-              = link_to namespace_project_job_path(project.namespace, project, build) do
+              = link_to project_job_path(project, build) do
                 %strong ##{build.id}
             - else
               %strong ##{build.id}
diff --git a/app/views/admin/users/projects.html.haml b/app/views/admin/users/projects.html.haml
index 15eaf1c0e67f16c845e0404646a22a0140a2df9f..4a440f3f6d4182d2d9428ff25988ae9504fc52ab 100644
--- a/app/views/admin/users/projects.html.haml
+++ b/app/views/admin/users/projects.html.haml
@@ -33,7 +33,7 @@
           - member = project.team.find_member(@user.id)
           %li.project_member
             .list-item-name
-              = link_to admin_namespace_project_path(project.namespace, project), class: dom_class(project) do
+              = link_to admin_project_path(project), class: dom_class(project) do
                 = project.name_with_namespace
 
             - if member
@@ -44,5 +44,5 @@
                   %span.light.vertical-align-middle= member.human_access
 
                   - if member.respond_to? :project
-                    = link_to namespace_project_project_member_path(project.namespace, project, member), data: { confirm: remove_member_message(member) }, remote: true, method: :delete, class: "btn-xs btn btn-remove prepend-left-10", title: 'Remove user from project' do
+                    = link_to project_project_member_path(project, member), data: { confirm: remove_member_message(member) }, remote: true, method: :delete, class: "btn-xs btn btn-remove prepend-left-10", title: 'Remove user from project' do
                       %i.fa.fa-times
diff --git a/app/views/dashboard/activity.html.haml b/app/views/dashboard/activity.html.haml
index f893c3e1675604e3d15ce1d83fb76d8e6e697c1a..ad35d05c29afcfa9ab053a2bb8db4f5f9fe413bc 100644
--- a/app/views/dashboard/activity.html.haml
+++ b/app/views/dashboard/activity.html.haml
@@ -1,3 +1,4 @@
+- @hide_top_links = true
 - @no_container = true
 
 = content_for :meta_tags do
diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml
index f9b45a539a119c2f6fb471b6426f93d726d53973..1cea81827330dd0cc687abf434583b66e80a0948 100644
--- a/app/views/dashboard/groups/index.html.haml
+++ b/app/views/dashboard/groups/index.html.haml
@@ -1,3 +1,4 @@
+- @hide_top_links = true
 - page_title "Groups"
 - header_title  "Groups", dashboard_groups_path
 = render 'dashboard/groups_head'
diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml
index 664ec618b7965375fea11b547dad4055faeaf0f5..ef1467c4d78538614a7b96078aa8fcc6c10a7960 100644
--- a/app/views/dashboard/milestones/index.html.haml
+++ b/app/views/dashboard/milestones/index.html.haml
@@ -1,3 +1,4 @@
+- @hide_top_links = true
 - page_title 'Milestones'
 - header_title 'Milestones', dashboard_milestones_path
 
diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml
index 5e63a61e21b21062888fba6c9e016e0932a93b4c..7ac6cf06fb9e700554c547fdc2add478396c432c 100644
--- a/app/views/dashboard/projects/index.html.haml
+++ b/app/views/dashboard/projects/index.html.haml
@@ -1,4 +1,6 @@
 - @no_container = true
+- @hide_top_links = true
+- @breadcrumb_title = "Projects"
 
 = content_for :meta_tags do
   = auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity")
diff --git a/app/views/dashboard/snippets/index.html.haml b/app/views/dashboard/snippets/index.html.haml
index 85cbe0bf0e6e7a6e754c8a0b9413c6be95c1ab7b..e86b1ab311661ab69cde3562fdfde893e697cf71 100644
--- a/app/views/dashboard/snippets/index.html.haml
+++ b/app/views/dashboard/snippets/index.html.haml
@@ -1,3 +1,4 @@
+- @hide_top_links = true
 - page_title    "Snippets"
 - header_title  "Snippets", dashboard_snippets_path
 
diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml
index d696577278d155a8c00f940a14f1f90435493bb2..298604dee8ca200ca8edf28596d4ea9a8457a0c9 100644
--- a/app/views/devise/shared/_signup_box.html.haml
+++ b/app/views/devise/shared/_signup_box.html.haml
@@ -25,7 +25,7 @@
       %div
       - if Gitlab::Recaptcha.enabled?
         = recaptcha_tags
-      %div
+      .submit-container
         = f.submit "Register", class: "btn-register btn"
 .clearfix.submit-container
   %p
diff --git a/app/views/discussions/_new_issue_for_all_discussions.html.haml b/app/views/discussions/_new_issue_for_all_discussions.html.haml
index ca9e0e8728a47b3532b0cd8e4534514477c23174..cab346fb514abe03ef076cd9c0362513eeb5c8fa 100644
--- a/app/views/discussions/_new_issue_for_all_discussions.html.haml
+++ b/app/views/discussions/_new_issue_for_all_discussions.html.haml
@@ -3,4 +3,4 @@
     .btn.btn-default.discussion-create-issue-btn.has-tooltip{ title: "Resolve all discussions in new issue",
                                                              "aria-label" => "Resolve all discussions in a new issue",
                                                              "data-container" => "body" }
-      = link_to custom_icon('icon_mr_issue'), new_namespace_project_issue_path(@project.namespace, @project, merge_request_to_resolve_discussions_of: merge_request.iid), title: "Resolve all discussions in new issue", class: 'new-issue-for-discussion'
+      = link_to custom_icon('icon_mr_issue'), new_project_issue_path(@project, merge_request_to_resolve_discussions_of: merge_request.iid), title: "Resolve all discussions in new issue", class: 'new-issue-for-discussion'
diff --git a/app/views/discussions/_new_issue_for_discussion.html.haml b/app/views/discussions/_new_issue_for_discussion.html.haml
index df5546a1e32d76573e8f4e3c7c5bcfa5d382ef79..a9bc317b8f837cf94505d37a92dda96fb1aa76be 100644
--- a/app/views/discussions/_new_issue_for_discussion.html.haml
+++ b/app/views/discussions/_new_issue_for_discussion.html.haml
@@ -5,4 +5,4 @@
       .btn.btn-default.discussion-create-issue-btn.has-tooltip{ title: "Resolve this discussion in a new issue",
                                                                "aria-label" => "Resolve this discussion in a new issue",
                                                                "data-container" => "body" }
-        = link_to custom_icon('icon_mr_issue'), new_namespace_project_issue_path(@project.namespace, @project, merge_request_to_resolve_discussions_of: merge_request.iid, discussion_to_resolve: discussion.id), title: "Resolve this discussion in a new issue", class: 'new-issue-for-discussion'
+        = link_to custom_icon('icon_mr_issue'), new_project_issue_path(@project, merge_request_to_resolve_discussions_of: merge_request.iid, discussion_to_resolve: discussion.id), title: "Resolve this discussion in a new issue", class: 'new-issue-for-discussion'
diff --git a/app/views/doorkeeper/applications/edit.html.haml b/app/views/doorkeeper/applications/edit.html.haml
index fb6aa30acee484c715027673468ff9b1f3083a3c..49f90298a508358487b2ab043cb8498fc97be582 100644
--- a/app/views/doorkeeper/applications/edit.html.haml
+++ b/app/views/doorkeeper/applications/edit.html.haml
@@ -1,3 +1,4 @@
 - page_title "Edit", @application.name, "Applications"
+- @content_class = "limit-container-width" unless fluid_layout
 %h3.page-title Edit application
 = render 'form', application: @application
diff --git a/app/views/doorkeeper/applications/index.html.haml b/app/views/doorkeeper/applications/index.html.haml
index aa271150b07f082d85d0906f3f7d8eeacd6af9f5..d1237d7bf6f7ab6d7664b2d2c1fb7b7eecb6f910 100644
--- a/app/views/doorkeeper/applications/index.html.haml
+++ b/app/views/doorkeeper/applications/index.html.haml
@@ -1,7 +1,8 @@
 - page_title "Applications"
+- @content_class = "limit-container-width" unless fluid_layout
 
 .row.prepend-top-default
-  .col-lg-3.profile-settings-sidebar
+  .col-lg-4.profile-settings-sidebar
     %h4.prepend-top-0
       = page_title
     %p
@@ -10,7 +11,7 @@
         and applications that you've authorized to use your account.
       - else
         Manage applications that you've authorized to use your account.
-  .col-lg-9
+  .col-lg-8
     - if user_oauth_applications?
       %h5.prepend-top-0
         Add new application
diff --git a/app/views/doorkeeper/applications/show.html.haml b/app/views/doorkeeper/applications/show.html.haml
index 559de63d96dd3c153fe61aa0becf7de33d1411ff..72eab964766fd8ec5f9f55fc1c9a6aa5a073de61 100644
--- a/app/views/doorkeeper/applications/show.html.haml
+++ b/app/views/doorkeeper/applications/show.html.haml
@@ -1,4 +1,6 @@
 - page_title @application.name, "Applications"
+- @content_class = "limit-container-width" unless fluid_layout
+
 %h3.page-title
   Application: #{@application.name}
 
diff --git a/app/views/events/_commit.html.haml b/app/views/events/_commit.html.haml
index 3c64f1be5ffb44fa30a09b1d7fc339771f564c25..ad434a6455650a925f3e9ae8bec492c4cc368f36 100644
--- a/app/views/events/_commit.html.haml
+++ b/app/views/events/_commit.html.haml
@@ -1,5 +1,5 @@
 %li.commit
   .commit-row-title
-    = link_to truncate_sha(commit[:id]), namespace_project_commit_path(project.namespace, project, commit[:id]), class: "commit-sha", alt: '', title: truncate_sha(commit[:id])
+    = link_to truncate_sha(commit[:id]), project_commit_path(project, commit[:id]), class: "commit-sha", alt: '', title: truncate_sha(commit[:id])
     &middot;
     = markdown event_commit_title(commit[:message]), project: project, pipeline: :single_line, author: event.author
diff --git a/app/views/events/_event_push.atom.haml b/app/views/events/_event_push.atom.haml
index f8f0bcb76082c754614b66548de67a78b3f2bbf3..9fcacfbbf3699e194074e938e50300c368dfd1cf 100644
--- a/app/views/events/_event_push.atom.haml
+++ b/app/views/events/_event_push.atom.haml
@@ -2,7 +2,7 @@
   - event.commits.first(15).each do |commit|
     %p
       %strong= commit[:author][:name]
-      = link_to "(##{truncate_sha(commit[:id])})", namespace_project_commit_path(event.project.namespace, event.project, id: commit[:id])
+      = link_to "(##{truncate_sha(commit[:id])})", project_commit_path(event.project, id: commit[:id])
       %i
         at
         = commit[:timestamp].to_time.to_s(:short)
diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml
index 769ac655d0a54f36f419f2694d78bdf4425de0cf..54b414cc62abe0e326936e21deaf1a097d375a52 100644
--- a/app/views/events/event/_push.html.haml
+++ b/app/views/events/event/_push.html.haml
@@ -6,7 +6,7 @@
   %span.author_name= link_to_author event
   %span.pushed #{event.action_name} #{event.ref_type}
   %strong
-    - commits_link = namespace_project_commits_path(project.namespace, project, event.ref_name)
+    - commits_link = project_commits_path(project, event.ref_name)
     = link_to_if project.repository.branch_exists?(event.ref_name), event.ref_name, commits_link, class: 'ref-name'
 
   = render "events/event_scope", event: event
@@ -31,7 +31,7 @@
             - from = project.default_branch
             - from_label = from
 
-          = link_to namespace_project_compare_path(project.namespace, project, from: from, to: event.commit_to) do
+          = link_to project_compare_path(project, from: from, to: event.commit_to) do
             Compare #{from_label}...#{truncate_sha(event.commit_to)}
 
           - if create_mr
diff --git a/app/views/groups/_home_panel.html.haml b/app/views/groups/_home_panel.html.haml
index 41f54f6bf4295d2bd3eb545077490bf9f32edd64..181c7bee702657bbaa97eeb5a43f05cfa1aa87d8 100644
--- a/app/views/groups/_home_panel.html.haml
+++ b/app/views/groups/_home_panel.html.haml
@@ -3,7 +3,7 @@
     .avatar-container.s70.group-avatar
       = image_tag group_icon(@group), class: "avatar s70 avatar-tile"
     %h1.group-title
-      @#{@group.path}
+      = @group.name
       %span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) }
         = visibility_level_icon(@group.visibility_level, fw: false)
 
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index 7d5add3cc1ca0d344d39ce2818722e4da722c7ba..9ebb3894c55fbea0f97931919d105c6352eeb445 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -45,10 +45,13 @@
 .panel.panel-danger
   .panel-heading Remove group
   .panel-body
-    %p
-      Removing group will cause all child projects and resources to be removed.
-      %br
-      %strong Removed group can not be restored!
+    = form_tag(@group, method: :delete) do
+      %p
+        Removing group will cause all child projects and resources to be removed.
+        %br
+        %strong Removed group can not be restored!
 
-    .form-actions
-      = link_to 'Remove group', @group, data: {confirm: 'Removed group can not be restored! Are you sure?'}, method: :delete, class: "btn btn-remove"
+      .form-actions
+        = button_to 'Remove group', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_group_message(@group) }
+
+= render 'shared/confirm_modal', phrase: @group.path
diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml
index 8fe0bd149f36b3473dfa8ac53e6f8bcb18744185..45e39252e1632706bd5e3b2beb086f2e9b9dc4fc 100644
--- a/app/views/groups/merge_requests.html.haml
+++ b/app/views/groups/merge_requests.html.haml
@@ -18,5 +18,4 @@
     - 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/projects.html.haml b/app/views/groups/projects.html.haml
index 62ad47972b9e2462259d4a95041d787dfb72d4bf..7a2e688a1144ae93f15bf0a3efa2855fbce1097b 100644
--- a/app/views/groups/projects.html.haml
+++ b/app/views/groups/projects.html.haml
@@ -20,8 +20,8 @@
             %span.label.label-warning archived
           %span.badge
             = storage_counter(project.statistics.storage_size)
-          = link_to 'Members', namespace_project_project_members_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm"
-          = link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm"
+          = link_to 'Members', project_project_members_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-sm"
+          = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-sm"
           = link_to 'Remove', project, data: { confirm: remove_project_message(project)}, method: :delete, class: "btn btn-sm btn-remove"
     - if @projects.blank?
       .nothing-here-block This group has no projects yet
diff --git a/app/views/import/base/create.js.haml b/app/views/import/base/create.js.haml
index 57e8c3ca1e1d3e450787f884d62d72b06c7b5792..fde671e25a9c841434f59aee96c296dfe5981f4f 100644
--- a/app/views/import/base/create.js.haml
+++ b/app/views/import/base/create.js.haml
@@ -4,7 +4,7 @@
     job.attr("id", "project_#{@project.id}")
     target_field = job.find(".import-target")
     target_field.empty()
-    target_field.append('#{link_to @project.path_with_namespace, namespace_project_path(@project.namespace, @project)}')
+    target_field.append('#{link_to @project.path_with_namespace, project_path(@project)}')
     $("table.import-jobs tbody").prepend(job)
     job.addClass("active").find(".import-actions").html("<i class='fa fa-spinner fa-spin'></i> started")
 - else
diff --git a/app/views/invites/show.html.haml b/app/views/invites/show.html.haml
index 882fdf1317de821771ed861b15899c4fa437b838..ad6213b4efd0b7d2dc22b491481e626392fac3d1 100644
--- a/app/views/invites/show.html.haml
+++ b/app/views/invites/show.html.haml
@@ -12,7 +12,7 @@
     - project = @member.source
     project
     %strong
-      = link_to project.name_with_namespace, namespace_project_url(project.namespace, project)
+      = link_to project.name_with_namespace, project_url(project)
   - when Group
     - group = @member.source
     group
diff --git a/app/views/issues/_issue.atom.builder b/app/views/issues/_issue.atom.builder
index 2ed78bb3b658640e3b9299a4e0ffb265742e8db2..0c113c08526fb0906cb9907706e6c275c1190fdb 100644
--- a/app/views/issues/_issue.atom.builder
+++ b/app/views/issues/_issue.atom.builder
@@ -1,6 +1,6 @@
 xml.entry do
-  xml.id      namespace_project_issue_url(issue.project.namespace, issue.project, issue)
-  xml.link    href: namespace_project_issue_url(issue.project.namespace, issue.project, issue)
+  xml.id      project_issue_url(issue.project, issue)
+  xml.link    href: project_issue_url(issue.project, issue)
   xml.title   truncate(issue.title, length: 80)
   xml.updated issue.updated_at.xmlschema
   xml.media   :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(issue.author_email))
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index eea33b5966f729bb3a81924f76596f24f1227511..abb6dc2e9f300f96009bda9ef370e7f15e840be7 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -11,6 +11,8 @@
   %meta{ property: 'og:title',       content: page_title }
   %meta{ property: 'og:description', content: page_description }
   %meta{ property: 'og:image',       content: page_image }
+  %meta{ property: 'og:image:width', content: '64' }
+  %meta{ property: 'og:image:height', content: '64' }
   %meta{ property: 'og:url',         content: request.base_url + request.fullpath }
 
   -# Twitter Card - https://dev.twitter.com/cards/types/summary
@@ -30,9 +32,13 @@
   = stylesheet_link_tag "test",        media: "all" if Rails.env.test?
   = stylesheet_link_tag 'peek' if peek_enabled?
 
+  - if show_new_nav?
+    = stylesheet_link_tag "new_nav", media: "all"
+    = stylesheet_link_tag "new_sidebar", media: "all"
+
   = Gon::Base.render_data
 
-  = webpack_bundle_tag "runtime"
+  = webpack_bundle_tag "webpack_runtime"
   = webpack_bundle_tag "common"
   = webpack_bundle_tag "locale"
   = webpack_bundle_tag "main"
diff --git a/app/views/layouts/_init_auto_complete.html.haml b/app/views/layouts/_init_auto_complete.html.haml
index 6caaba240bb489ea978653a5e365f00743927693..4bb0dfc73fdc88ac31d64b8320f55bdf01e92ae0 100644
--- a/app/views/layouts/_init_auto_complete.html.haml
+++ b/app/views/layouts/_init_auto_complete.html.haml
@@ -5,10 +5,10 @@
   :javascript
     gl.GfmAutoComplete = gl.GfmAutoComplete || {};
     gl.GfmAutoComplete.dataSources = {
-      members: "#{members_namespace_project_autocomplete_sources_path(project.namespace, project, type: noteable_type, type_id: params[:id])}",
-      issues: "#{issues_namespace_project_autocomplete_sources_path(project.namespace, project)}",
-      mergeRequests: "#{merge_requests_namespace_project_autocomplete_sources_path(project.namespace, project)}",
-      labels: "#{labels_namespace_project_autocomplete_sources_path(project.namespace, project)}",
-      milestones: "#{milestones_namespace_project_autocomplete_sources_path(project.namespace, project)}",
-      commands: "#{commands_namespace_project_autocomplete_sources_path(project.namespace, project, type: noteable_type, type_id: params[:id])}"
+      members: "#{members_project_autocomplete_sources_path(project, type: noteable_type, type_id: params[:id])}",
+      issues: "#{issues_project_autocomplete_sources_path(project)}",
+      mergeRequests: "#{merge_requests_project_autocomplete_sources_path(project)}",
+      labels: "#{labels_project_autocomplete_sources_path(project)}",
+      milestones: "#{milestones_project_autocomplete_sources_path(project)}",
+      commands: "#{commands_project_autocomplete_sources_path(project, type: noteable_type, type_id: params[:id])}"
     };
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index b7df11681d33ab4de2dcde879d056c43b11cee6e..1a9f5401a78a9e16e5e0221e803ba23e30f6a47c 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -1,15 +1,21 @@
-.page-with-sidebar{ class: page_gutter_class }
-  - if defined?(nav) && nav
-    .layout-nav
-      .container-fluid
-        = render "layouts/nav/#{nav}"
-  - if content_for?(:sub_nav)
-    = yield :sub_nav
-  .content-wrapper{ class: layout_nav_class }
+.page-with-sidebar{ class: "#{('page-with-new-sidebar' if defined?(@new_sidebar) && @new_sidebar)} #{page_gutter_class}" }
+  - if show_new_nav?
+    - if defined?(nav) && nav
+      = render "layouts/nav/#{nav}"
+  - else
+    - if defined?(nav) && nav
+      .layout-nav
+        .container-fluid
+          = render "layouts/nav/#{nav}"
+    - if content_for?(:sub_nav)
+      = yield :sub_nav
+  .content-wrapper{ class: "#{(layout_nav_class unless show_new_nav?)}" }
     .alert-wrapper
       = render "layouts/broadcast"
       = render "layouts/flash"
       = yield :flash_message
+    - if show_new_nav?
+      = render "layouts/nav/breadcrumbs"
     %div{ class: "#{(container_class unless @no_container)} #{@content_class}" }
       .content{ id: "content-body" }
         = yield
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index b689991bb6d67a6c596d8b372a8ec164891d89d9..59f16b47bf7442fa834a90d25ba122e7b418128b 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -5,7 +5,7 @@
 - if @group && @group.persisted? && @group.path
   - group_data_attrs = { group_path: j(@group.path), name: @group.name, issues_path: issues_group_path(j(@group.path)), mr_path: merge_requests_group_path(j(@group.path)) }
 - if @project && @project.persisted?
-  - project_data_attrs = { project_path: j(@project.path), name: j(@project.name), issues_path: namespace_project_issues_path(@project.namespace, @project), mr_path: namespace_project_merge_requests_path(@project.namespace, @project) }
+  - project_data_attrs = { project_path: j(@project.path), name: j(@project.name), issues_path: project_issues_path(@project), mr_path: project_merge_requests_path(@project) }
 .search.search-form{ class: "#{'has-location-badge' if label.present?}" }
   = form_tag search_path, method: :get, class: 'navbar-form' do |f|
     .search-input-container
diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml
index 87064cc9b3f2261117732543da2928e8a21bdad1..ae9eee215e02d7ad6b1fbe2125d336022f27b058 100644
--- a/app/views/layouts/admin.html.haml
+++ b/app/views/layouts/admin.html.haml
@@ -1,5 +1,9 @@
 - page_title    "Admin Area"
 - header_title  "Admin Area", admin_root_path
-- nav           "admin"
+- if show_new_nav?
+  - nav         "new_admin_sidebar"
+  - @new_sidebar = true
+- else
+  - nav         "admin"
 
 = render template: "layouts/application"
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 2b07273a0a84debea7f08db8b7df92da2355ce0e..d879df8fc820cbf617ebfd0bc40e4b86758a9a77 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -4,7 +4,10 @@
   %body{ class: @body_class, data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}" } }
     = render "layouts/init_auto_complete" if @gfm_form
     = render 'peek/bar'
-    = render "layouts/header/default", title: header_title
+    - if show_new_nav?
+      = render "layouts/header/new"
+    - else
+      = render "layouts/header/default", title: header_title
     = render 'layouts/page', sidebar: sidebar, nav: nav
 
     = yield :scripts_body
diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml
index f06acc98ca1dbbdeaeeb7141fd6fa12948070428..35abfa0e80c730619c946a849712dce0c17bc404 100644
--- a/app/views/layouts/group.html.haml
+++ b/app/views/layouts/group.html.haml
@@ -1,6 +1,10 @@
 - page_title       @group.name
 - page_description @group.description  unless page_description
 - header_title     group_title(@group) unless header_title
-- nav              "group"
+- if show_new_nav?
+  - nav            "new_group_sidebar"
+  - @new_sidebar = true
+- else
+  - nav            "group"
 
 = render template: "layouts/application"
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 249253f49069acf7968c65878567746d851020ed..ed44263741e3a72b361b2e8d103e7484e6fa0306 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -17,7 +17,7 @@
         = link_to root_path, class: 'home', title: 'Dashboard', id: 'logo' do
           = brand_header_logo
 
-      .title-container
+      .title-container.js-title-container
         %h1.title{ class: ('initializing' if @has_group_title) }= title
 
       .navbar-collapse.collapse
@@ -74,6 +74,8 @@
                     = link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username }
                   %li
                     = link_to "Settings", profile_path
+                  %li
+                    = link_to "Turn on new nav", profile_preferences_path(anchor: "new-navigation")
                   %li.divider
                   %li
                     = link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link"
@@ -93,4 +95,4 @@
 - if @project && !@project.empty_repo?
   - if ref = @ref || @project.repository.root_ref
     :javascript
-      var findFileURL = "#{namespace_project_find_file_path(@project.namespace, @project, ref)}";
+      var findFileURL = "#{project_find_file_path(@project, ref)}";
diff --git a/app/views/layouts/header/_new.html.haml b/app/views/layouts/header/_new.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..bee7291da459561f4375699437a90bce27c589c9
--- /dev/null
+++ b/app/views/layouts/header/_new.html.haml
@@ -0,0 +1,91 @@
+%header.navbar.navbar-gitlab.navbar-gitlab-new{ class: nav_header_class }
+  %a.sr-only.gl-accessibility{ href: "#content-body", tabindex: "1" } Skip to content
+  .container-fluid
+    .header-content
+      .title-container
+        %h1.title
+          = link_to root_path, title: 'Dashboard' do
+            = brand_header_logo
+            %span.hidden-xs
+              GitLab
+
+        - if current_user
+          = render "layouts/nav/new_dashboard"
+        - else
+          = render "layouts/nav/new_explore"
+
+      .navbar-collapse.collapse
+        %ul.nav.navbar-nav
+          %li.hidden-sm.hidden-xs
+            = render 'layouts/search' unless current_controller?(:search)
+          %li.visible-sm-inline-block.visible-xs-inline-block
+            = link_to search_path, title: 'Search', aria: { label: "Search" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+              = icon('search')
+          - if current_user
+            - if session[:impersonator_id]
+              %li.impersonation
+                = link_to admin_impersonation_path, method: :delete, title: "Stop impersonation", aria: { label: 'Stop impersonation' }, data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
+                  = icon('user-secret fw')
+            - if current_user.admin?
+              %li
+                = link_to admin_root_path, title: 'Admin area', aria: { label: "Admin area" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+                  = icon('wrench fw')
+            = render 'layouts/header/new_dropdown'
+            - if Gitlab::Sherlock.enabled?
+              %li
+                = link_to sherlock_transactions_path, title: 'Sherlock Transactions',
+                  data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+                  = icon('tachometer fw')
+            %li
+              = link_to assigned_issues_dashboard_path, title: 'Issues', aria: { label: "Issues" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+                = icon('hashtag fw')
+                - issues_count = assigned_issuables_count(:issues)
+                %span.badge.issues-count{ class: ('hidden' if issues_count.zero?) }
+                  = number_with_delimiter(issues_count)
+            %li
+              = link_to assigned_mrs_dashboard_path, title: 'Merge requests', aria: { label: "Merge requests" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+                = custom_icon('mr_bold')
+                - merge_requests_count = assigned_issuables_count(:merge_requests)
+                %span.badge.merge-requests-count{ class: ('hidden' if merge_requests_count.zero?) }
+                  = number_with_delimiter(merge_requests_count)
+            %li
+              = link_to dashboard_todos_path, title: 'Todos', aria: { label: "Todos" }, class: 'shortcuts-todos', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+                = icon('check-circle fw')
+                %span.badge.todos-count{ class: ('hidden' if todos_pending_count.zero?) }
+                  = todos_count_format(todos_pending_count)
+            %li.header-user.dropdown
+              = link_to current_user, class: "header-user-dropdown-toggle", data: { toggle: "dropdown" } do
+                = image_tag avatar_icon(current_user, 26), width: 26, height: 26, class: "header-user-avatar"
+                = icon('chevron-down')
+              .dropdown-menu-nav.dropdown-menu-align-right
+                %ul
+                  %li.current-user
+                    .user-name.bold
+                      = current_user.name
+                    @#{current_user.username}
+                  %li.divider
+                  %li
+                    = link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username }
+                  %li
+                    = link_to "Settings", profile_path
+                  %li
+                    = link_to "Turn off new nav", profile_preferences_path(anchor: "new-navigation")
+                  %li.divider
+                  %li
+                    = link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link"
+          - else
+            %li
+              %div
+                = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-success'
+
+      %button.navbar-toggle.hidden-sm.hidden-md.hidden-lg{ type: 'button' }
+        %span.sr-only Toggle navigation
+        = icon('ellipsis-v', class: 'js-navbar-toggle-right')
+        = icon('times', class: 'js-navbar-toggle-left', style: 'display: none;')
+
+= render 'shared/outdated_browser'
+
+- if @project && !@project.empty_repo?
+  - if ref = @ref || @project.repository.root_ref
+    :javascript
+      var findFileURL = "#{project_find_file_path(@project, ref)}";
diff --git a/app/views/layouts/header/_new_dropdown.haml b/app/views/layouts/header/_new_dropdown.haml
index c730241438678d02b04684174cb894aa7ce07b1b..9da739b097400fa6a574eea64d36385e7704e802 100644
--- a/app/views/layouts/header/_new_dropdown.haml
+++ b/app/views/layouts/header/_new_dropdown.haml
@@ -1,10 +1,14 @@
 %li.header-new.dropdown
   = link_to new_project_path, class: "header-new-dropdown-toggle has-tooltip", title: "New...", ref: 'tooltip', aria: { label: "New..." }, data: { toggle: 'dropdown', placement: 'bottom', container: 'body' } do
-    = icon('plus fw')
-    = icon('caret-down')
+    - if show_new_nav?
+      = icon('plus')
+      = icon('chevron-down')
+    - else
+      = icon('plus fw')
+      = icon('caret-down')
   .dropdown-menu-nav.dropdown-menu-align-right
     %ul
-      - if @group
+      - if @group&.persisted?
         - create_group_project = can?(current_user, :create_projects, @group)
         - create_group_subgroup = can?(current_user, :create_subgroup, @group)
         - if create_group_project || create_group_subgroup
@@ -18,7 +22,7 @@
           %li.divider
           %li.dropdown-bold-header GitLab
 
-      - if @project && @project.persisted?
+      - if @project&.persisted?
         - create_project_issue = can?(current_user, :create_issue, @project)
         - merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
         - create_project_snippet = can?(current_user, :create_project_snippet, @project)
@@ -26,13 +30,13 @@
           %li.dropdown-bold-header This project
           - if create_project_issue
             %li
-              = link_to 'New issue', new_namespace_project_issue_path(@project.namespace, @project)
+              = link_to 'New issue', new_project_issue_path(@project)
           - if merge_project
             %li
-              = link_to 'New merge request', new_namespace_project_merge_request_path(merge_project.namespace, merge_project)
+              = link_to 'New merge request', project_new_merge_request_path(merge_project)
           - if create_project_snippet
             %li.header-new-project-snippet
-              = link_to 'New snippet', new_namespace_project_snippet_path(@project.namespace, @project)
+              = link_to 'New snippet', new_project_snippet_path(@project)
           %li.divider
           %li.dropdown-bold-header GitLab
       - if current_user.can_create_project?
diff --git a/app/views/layouts/nav/_breadcrumbs.html.haml b/app/views/layouts/nav/_breadcrumbs.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..5f1641f43001c4e34e18cbeffa8c5f6179ef04d3
--- /dev/null
+++ b/app/views/layouts/nav/_breadcrumbs.html.haml
@@ -0,0 +1,19 @@
+- breadcrumb_title = @breadcrumb_title || controller.controller_name.humanize
+- hide_top_links = @hide_top_links || false
+
+%nav.breadcrumbs{ role: "navigation" }
+  .breadcrumbs-container{ class: container_class }
+    .breadcrumbs-links.js-title-container
+      - unless hide_top_links
+        .title
+          = link_to "GitLab", root_path
+          \/
+          = header_title
+      %h2.breadcrumbs-sub-title
+        %ul.list-unstyled
+          - if content_for?(:sub_title_before)
+            = yield :sub_title_before
+          %li= link_to breadcrumb_title, request.path
+    - if content_for?(:breadcrumbs_extra)
+      .breadcrumbs-extra.hidden-xs= yield :breadcrumbs_extra
+    = yield :header_content
diff --git a/app/views/layouts/nav/_new_admin_sidebar.html.haml b/app/views/layouts/nav/_new_admin_sidebar.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..40c1ca7b53e62ae46da33f65ee9391c4b038e354
--- /dev/null
+++ b/app/views/layouts/nav/_new_admin_sidebar.html.haml
@@ -0,0 +1,123 @@
+.nav-sidebar
+  = link_to admin_root_path, title: 'Admin Overview', class: 'context-header' do
+    .avatar-container.s40.settings-avatar
+      = icon('wrench')
+    .project-title Admin Area
+  %ul.sidebar-top-level-items
+    = nav_link(controller: %w(dashboard admin projects users groups builds runners cohorts), html_options: {class: 'home'}) do
+      = link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do
+        %span
+          Overview
+
+      %ul.sidebar-sub-level-items
+        = nav_link(controller: :dashboard, html_options: {class: 'home'}) do
+          = link_to admin_root_path, title: 'Overview' do
+            %span
+              Overview
+        = nav_link(controller: [:admin, :projects]) do
+          = link_to admin_projects_path, title: 'Projects' do
+            %span
+              Projects
+        = nav_link(controller: :users) do
+          = link_to admin_users_path, title: 'Users' do
+            %span
+              Users
+        = nav_link(controller: :groups) do
+          = link_to admin_groups_path, title: 'Groups' do
+            %span
+              Groups
+        = nav_link path: 'builds#index' do
+          = link_to admin_jobs_path, title: 'Jobs' do
+            %span
+              Jobs
+        = nav_link path: ['runners#index', 'runners#show'] do
+          = link_to admin_runners_path, title: 'Runners' do
+            %span
+              Runners
+        = nav_link path: 'cohorts#index' do
+          = link_to admin_cohorts_path, title: 'Cohorts' do
+            %span
+              Cohorts
+
+    = nav_link(controller: %w(conversational_development_index system_info background_jobs logs health_check requests_profiles)) do
+      = link_to admin_conversational_development_index_path, title: 'Monitoring' do
+        %span
+          Monitoring
+
+      %ul.sidebar-sub-level-items
+        = nav_link(controller: :conversational_development_index) do
+          = link_to admin_conversational_development_index_path, title: 'ConvDev Index' do
+            %span
+              ConvDev Index
+        = nav_link(controller: :system_info) do
+          = link_to admin_system_info_path, title: 'System Info' do
+            %span
+              System Info
+        = nav_link(controller: :background_jobs) do
+          = link_to admin_background_jobs_path, title: 'Background Jobs' do
+            %span
+              Background Jobs
+        = nav_link(controller: :logs) do
+          = link_to admin_logs_path, title: 'Logs' do
+            %span
+              Logs
+        = nav_link(controller: :health_check) do
+          = link_to admin_health_check_path, title: 'Health Check' do
+            %span
+              Health Check
+        = nav_link(controller: :requests_profiles) do
+          = link_to admin_requests_profiles_path, title: 'Requests Profiles' do
+            %span
+              Requests Profiles
+
+    = nav_link(controller: :broadcast_messages) do
+      = link_to admin_broadcast_messages_path, title: 'Messages' do
+        %span
+          Messages
+    = nav_link(controller: [:hooks, :hook_logs]) do
+      = link_to admin_hooks_path, title: 'Hooks' do
+        %span
+          System Hooks
+
+    = nav_link(controller: :applications) do
+      = link_to admin_applications_path, title: 'Applications' do
+        %span
+          Applications
+
+    = nav_link(controller: :abuse_reports) do
+      = link_to admin_abuse_reports_path, title: "Abuse Reports" do
+        %span
+          Abuse Reports
+          %span.badge.count= number_with_delimiter(AbuseReport.count(:all))
+
+    - if akismet_enabled?
+      = nav_link(controller: :spam_logs) do
+        = link_to admin_spam_logs_path, title: "Spam Logs" do
+          %span
+            Spam Logs
+
+    = nav_link(controller: :deploy_keys) do
+      = link_to admin_deploy_keys_path, title: 'Deploy Keys' do
+        %span
+          Deploy Keys
+
+    = nav_link(controller: :services) do
+      = link_to admin_application_settings_services_path, title: 'Service Templates' do
+        %span
+          Service Templates
+
+    = nav_link(controller: :labels) do
+      = link_to admin_labels_path, title: 'Labels' do
+        %span
+          Labels
+
+    = nav_link(controller: :appearances) do
+      = link_to admin_appearances_path, title: 'Appearances' do
+        %span
+          Appearance
+
+    %li.divider
+    = nav_link(controller: :application_settings) do
+      = link_to admin_application_settings_path, title: 'Settings' do
+        %span
+          Settings
diff --git a/app/views/layouts/nav/_new_dashboard.html.haml b/app/views/layouts/nav/_new_dashboard.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..7109baa4dad17b102c81450acd1779f15360044a
--- /dev/null
+++ b/app/views/layouts/nav/_new_dashboard.html.haml
@@ -0,0 +1,33 @@
+%ul.list-unstyled.navbar-sub-nav
+  = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "home"}) do
+    = link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
+      Projects
+
+  = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
+    = link_to dashboard_groups_path, class: 'dashboard-shortcuts-groups', title: 'Groups' do
+      Groups
+
+  = nav_link(path: 'dashboard#activity', html_options: { class: "hidden-xs hidden-sm" }) do
+    = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do
+      Activity
+
+  %li.dropdown
+    %a{ href: "#", data: { toggle: "dropdown" } }
+      More
+      = icon("chevron-down", class: "dropdown-chevron")
+    .dropdown-menu
+      %ul
+        = nav_link(path: 'dashboard#activity', html_options: { class: "visible-xs visible-sm" }) do
+          = link_to activity_dashboard_path, title: 'Activity' do
+            Activity
+
+        = nav_link(controller: 'dashboard/milestones') do
+          = link_to dashboard_milestones_path, class: 'dashboard-shortcuts-milestones', title: 'Milestones' do
+            Milestones
+
+        = nav_link(controller: 'dashboard/snippets') do
+          = link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets', title: 'Snippets' do
+            Snippets
+        %li.divider
+        %li
+          = link_to "Help", help_path, title: 'About GitLab CE'
diff --git a/app/views/layouts/nav/_new_explore.html.haml b/app/views/layouts/nav/_new_explore.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..40385f251e39b38e292c7c5078f90e07ab9286a7
--- /dev/null
+++ b/app/views/layouts/nav/_new_explore.html.haml
@@ -0,0 +1,19 @@
+%ul.list-unstyled.navbar-sub-nav
+  = nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do
+    = link_to explore_root_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
+      Projects
+  = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
+    = link_to explore_groups_path, title: 'Groups', class: 'dashboard-shortcuts-groups' do
+      Groups
+  %li.dropdown
+    %a{ href: "#", data: { toggle: "dropdown" } }
+      More
+      = icon("chevron-down", class: "dropdown-chevron")
+    .dropdown-menu
+      %ul
+        = nav_link(controller: :snippets) do
+          = link_to explore_snippets_path, title: 'Snippets', class: 'dashboard-shortcuts-snippets' do
+            Snippets
+        %li.divider
+        %li
+          = link_to "Help", help_path, title: 'About GitLab CE'
diff --git a/app/views/layouts/nav/_new_group_sidebar.html.haml b/app/views/layouts/nav/_new_group_sidebar.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..b7ac04cc3e5f6709931e3bb08453fc792197402d
--- /dev/null
+++ b/app/views/layouts/nav/_new_group_sidebar.html.haml
@@ -0,0 +1,61 @@
+.nav-sidebar
+  = link_to group_path(@group), title: 'Group', class: 'context-header' do
+    .avatar-container.s40.group-avatar
+      = image_tag group_icon(@group), class: "avatar s40 avatar-tile"
+    .group-title
+      = @group.name
+  %ul.sidebar-top-level-items
+    = nav_link(path: ['groups#show', 'groups#activity', 'groups#subgroups'], html_options: { class: 'home' }) do
+      = link_to group_path(@group), title: 'Home' do
+        %span
+          Group
+
+      %ul.sidebar-sub-level-items
+        = nav_link(path: ['groups#show', 'groups#subgroups'], html_options: { class: 'home' }) do
+          = link_to group_path(@group), title: 'Group Home' do
+            %span
+              Home
+
+        = nav_link(path: 'groups#activity') do
+          = link_to activity_group_path(@group), title: 'Activity' do
+            %span
+              Activity
+
+    = nav_link(path: ['groups#issues', 'labels#index', 'milestones#index']) do
+      = link_to issues_group_path(@group), title: 'Issues' do
+        %span
+          Issues
+          - issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute
+          %span.badge.count= number_with_delimiter(issues.count)
+
+      %ul.sidebar-sub-level-items
+        = nav_link(path: 'groups#issues', html_options: { class: 'home' }) do
+          = link_to issues_group_path(@group), title: 'List' do
+            %span
+              List
+
+        = nav_link(path: 'labels#index') do
+          = link_to group_labels_path(@group), title: 'Labels' do
+            %span
+              Labels
+
+        = nav_link(path: 'milestones#index') do
+          = link_to group_milestones_path(@group), title: 'Milestones' do
+            %span
+              Milestones
+
+    = nav_link(path: 'groups#merge_requests') do
+      = link_to merge_requests_group_path(@group), title: 'Merge Requests' do
+        %span
+          Merge Requests
+          - merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute
+          %span.badge.count= number_with_delimiter(merge_requests.count)
+    = nav_link(path: 'group_members#index') do
+      = link_to group_group_members_path(@group), title: 'Members' do
+        %span
+          Members
+    - if current_user && can?(current_user, :admin_group, @group)
+      = nav_link(path: %w[groups#projects groups#edit]) do
+        = link_to edit_group_path(@group), title: 'Settings' do
+          %span
+            Settings
diff --git a/app/views/layouts/nav/_new_profile_sidebar.html.haml b/app/views/layouts/nav/_new_profile_sidebar.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..033ea149cfb766542b134adb2205ad3b12f47f55
--- /dev/null
+++ b/app/views/layouts/nav/_new_profile_sidebar.html.haml
@@ -0,0 +1,53 @@
+.nav-sidebar
+  = link_to profile_path, title: 'Profile Settings', class: 'context-header' do
+    .avatar-container.s40.settings-avatar
+      = icon('user')
+    .project-title User Settings
+  %ul.sidebar-top-level-items
+    = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
+      = link_to profile_path, title: 'Profile Settings' do
+        %span
+          Profile
+    = nav_link(controller: [:accounts, :two_factor_auths]) do
+      = link_to profile_account_path, title: 'Account' do
+        %span
+          Account
+    - if current_application_settings.user_oauth_applications?
+      = nav_link(controller: 'oauth/applications') do
+        = link_to applications_profile_path, title: 'Applications' do
+          %span
+            Applications
+    = nav_link(controller: :chat_names) do
+      = link_to profile_chat_names_path, title: 'Chat' do
+        %span
+          Chat
+    = nav_link(controller: :personal_access_tokens) do
+      = link_to profile_personal_access_tokens_path, title: 'Access Tokens' do
+        %span
+          Access Tokens
+    = nav_link(controller: :emails) do
+      = link_to profile_emails_path, title: 'Emails' do
+        %span
+          Emails
+    - unless current_user.ldap_user?
+      = nav_link(controller: :passwords) do
+        = link_to edit_profile_password_path, title: 'Password' do
+          %span
+            Password
+    = nav_link(controller: :notifications) do
+      = link_to profile_notifications_path, title: 'Notifications' do
+        %span
+          Notifications
+
+    = nav_link(controller: :keys) do
+      = link_to profile_keys_path, title: 'SSH Keys' do
+        %span
+          SSH Keys
+    = nav_link(controller: :preferences) do
+      = link_to profile_preferences_path, title: 'Preferences' do
+        %span
+          Preferences
+    = nav_link(path: 'profiles#audit_log') do
+      = link_to audit_log_profile_path, title: 'Authentication log' do
+        %span
+          Authentication log
diff --git a/app/views/layouts/nav/_new_project_sidebar.html.haml b/app/views/layouts/nav/_new_project_sidebar.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..6e483353a2dc02b3d4168055abdbccf11c79db7e
--- /dev/null
+++ b/app/views/layouts/nav/_new_project_sidebar.html.haml
@@ -0,0 +1,247 @@
+.nav-sidebar
+  - can_edit = can?(current_user, :admin_project, @project)
+  = link_to project_path(@project), title: 'Project', class: 'context-header' do
+    .avatar-container.s40.project-avatar
+      = project_icon(@project, alt: @project.name, class: 'avatar s40 avatar-tile')
+    .project-title
+      = @project.name
+  %ul.sidebar-top-level-items
+    = nav_link(path: ['projects#show', 'projects#activity', 'cycle_analytics#show'], html_options: { class: 'home' }) do
+      = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do
+        %span
+          Project
+
+      %ul.sidebar-sub-level-items
+        = nav_link(path: 'projects#show') do
+          = link_to project_path(@project), title: _('Project home'), class: 'shortcuts-project' do
+            %span= _('Home')
+
+        = nav_link(path: 'projects#activity') do
+          = link_to activity_project_path(@project), title: _('Activity'), class: 'shortcuts-project-activity' do
+            %span= _('Activity')
+
+        - if can?(current_user, :read_cycle_analytics, @project)
+          = nav_link(path: 'cycle_analytics#show') do
+            = link_to project_cycle_analytics_path(@project), title: _('Cycle Analytics'), class: 'shortcuts-project-cycle-analytics' do
+              %span= _('Cycle Analytics')
+
+    - if project_nav_tab? :files
+      = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare projects/repositories tags branches releases graphs network)) do
+        = link_to project_tree_path(@project), title: 'Repository',  class: 'shortcuts-tree' do
+          %span
+            Repository
+
+        %ul.sidebar-sub-level-items
+          = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do
+            = link_to project_tree_path(@project) do
+              #{ _('Files') }
+
+          = nav_link(controller: [:commit, :commits]) do
+            = link_to project_commits_path(@project, current_ref) do
+              #{ _('Commits') }
+
+          = nav_link(html_options: {class: branches_tab_class}) do
+            = link_to project_branches_path(@project) do
+              #{ _('Branches') }
+
+          = nav_link(controller: [:tags, :releases]) do
+            = link_to project_tags_path(@project) do
+              #{ _('Tags') }
+
+          = nav_link(path: 'graphs#show') do
+            = link_to project_graph_path(@project, current_ref) do
+              #{ _('Contributors') }
+
+          = nav_link(controller: %w(network)) do
+            = link_to project_network_path(@project, current_ref) do
+              #{ s_('ProjectNetworkGraph|Graph') }
+
+          = nav_link(controller: :compare) do
+            = link_to project_compare_index_path(@project, from: @repository.root_ref, to: current_ref) do
+              #{ _('Compare') }
+
+          = nav_link(path: 'graphs#charts') do
+            = link_to charts_project_graph_path(@project, current_ref) do
+              #{ _('Charts') }
+
+    - if project_nav_tab? :container_registry
+      = nav_link(controller: %w[projects/registry/repositories]) do
+        = link_to project_container_registry_index_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do
+          %span
+            Registry
+
+    - if project_nav_tab? :issues
+      = nav_link(controller: @project.default_issues_tracker? ? [:issues, :labels, :milestones, :boards] : :issues) do
+        = link_to project_issues_path(@project), title: 'Issues', class: 'shortcuts-issues' do
+          %span
+            Issues
+            - if @project.default_issues_tracker?
+              %span.badge.count.issue_counter= number_with_delimiter(IssuesFinder.new(current_user, project_id: @project.id).execute.opened.count)
+
+        %ul.sidebar-sub-level-items
+          - if project_nav_tab?(:issues) && !current_controller?(:merge_requests)
+            = nav_link(controller: :issues) do
+              = link_to project_issues_path(@project), title: 'Issues' do
+                %span
+                  List
+
+            = nav_link(controller: :boards) do
+              = link_to project_boards_path(@project), title: 'Board' do
+                %span
+                  Board
+
+          - if project_nav_tab?(:merge_requests) && current_controller?(:merge_requests)
+            = nav_link(controller: :merge_requests) do
+              = link_to project_merge_requests_path(@project), title: 'Merge Requests' do
+                %span
+                  Merge Requests
+
+          - if project_nav_tab? :labels
+            = nav_link(controller: :labels) do
+              = link_to project_labels_path(@project), title: 'Labels' do
+                %span
+                  Labels
+
+          - if project_nav_tab? :milestones
+            = nav_link(controller: :milestones) do
+              = link_to project_milestones_path(@project), title: 'Milestones' do
+                %span
+                  Milestones
+
+    - if project_nav_tab? :merge_requests
+      = nav_link(controller: @project.default_issues_tracker? ? :merge_requests : [:merge_requests, :labels, :milestones]) do
+        = link_to project_merge_requests_path(@project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
+          %span
+            Merge Requests
+            %span.badge.count.merge_counter.js-merge-counter= number_with_delimiter(MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.count)
+
+    - if project_nav_tab? :pipelines
+      = nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :environments, :artifacts]) do
+        = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
+          %span
+            Pipelines
+
+        %ul.sidebar-sub-level-items
+          - if project_nav_tab? :pipelines
+            = nav_link(path: ['pipelines#index', 'pipelines#show']) do
+              = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
+                %span
+                  Pipelines
+
+          - if project_nav_tab? :builds
+            = nav_link(controller: [:jobs, :artifacts]) do
+              = link_to project_jobs_path(@project), title: 'Jobs', class: 'shortcuts-builds' do
+                %span
+                  Jobs
+
+          - if project_nav_tab? :pipelines
+            = nav_link(controller: :pipeline_schedules) do
+              = link_to pipeline_schedules_path(@project), title: 'Schedules', class: 'shortcuts-builds' do
+                %span
+                  Schedules
+
+          - if project_nav_tab? :environments
+            = nav_link(controller: :environments) do
+              = link_to project_environments_path(@project), title: 'Environments', class: 'shortcuts-environments' do
+                %span
+                  Environments
+
+          - if @project.feature_available?(:builds, current_user) && !@project.empty_repo?
+            = nav_link(path: 'pipelines#charts') do
+              = link_to charts_project_pipelines_path(@project), title: 'Charts', class: 'shortcuts-pipelines-charts' do
+                %span
+                  Charts
+
+    - if project_nav_tab? :wiki
+      = nav_link(controller: :wikis) do
+        = link_to get_project_wiki_path(@project), title: 'Wiki', class: 'shortcuts-wiki' do
+          %span
+            Wiki
+
+    - if project_nav_tab? :snippets
+      = nav_link(controller: :snippets) do
+        = link_to project_snippets_path(@project), title: 'Snippets', class: 'shortcuts-snippets' do
+          %span
+            Snippets
+
+    - if project_nav_tab? :settings
+      = nav_link(path: %w[projects#edit members#show integrations#show services#edit repository#show ci_cd#show pages#show]) do
+        = link_to edit_project_path(@project), title: 'Settings', class: 'shortcuts-tree' do
+          %span
+            Settings
+
+        %ul.sidebar-sub-level-items
+          - can_edit = can?(current_user, :admin_project, @project)
+          - if can_edit
+            = nav_link(controller: :projects) do
+              = link_to edit_project_path(@project), title: 'General' do
+                %span
+                  General
+          = nav_link(controller: :members) do
+            = link_to project_settings_members_path(@project), title: 'Members' do
+              %span
+                Members
+          - if can_edit
+            = nav_link(controller: [:integrations, :services, :hooks, :hook_logs]) do
+              = link_to project_settings_integrations_path(@project), title: 'Integrations' do
+                %span
+                  Integrations
+            = nav_link(controller: :repository) do
+              = link_to project_settings_repository_path(@project), title: 'Repository' do
+                %span
+                  Repository
+            - if @project.feature_available?(:builds, current_user)
+              = nav_link(controller: :ci_cd) do
+                = link_to project_settings_ci_cd_path(@project), title: 'Pipelines' do
+                  %span
+                    Pipelines
+            - if Gitlab.config.pages.enabled
+              = nav_link(controller: :pages) do
+                = link_to project_pages_path(@project), title: 'Pages' do
+                  %span
+                    Pages
+
+    - else
+      = nav_link(path: %w[members#show]) do
+        = link_to project_settings_members_path(@project), title: 'Settings', class: 'shortcuts-tree' do
+          %span
+            Settings
+
+    -# Shortcut to Project > Activity
+    %li.hidden
+      = link_to activity_project_path(@project), title: 'Activity', class: 'shortcuts-project-activity' do
+        %span
+          Activity
+
+    -# Shortcut to Repository > Graph (formerly, Network)
+    - if project_nav_tab? :network
+      %li.hidden
+        = link_to project_network_path(@project, current_ref), title: 'Network', class: 'shortcuts-network' do
+          Graph
+
+    -# Shortcut to Repository > Charts (formerly, top-nav item "Graphs")
+    - unless @project.empty_repo?
+      %li.hidden
+        = link_to charts_project_graph_path(@project, current_ref), title: 'Charts', class: 'shortcuts-repository-charts' do
+          Charts
+
+    -# Shortcut to Issues > New Issue
+    %li.hidden
+      = link_to new_project_issue_path(@project), class: 'shortcuts-new-issue' do
+        Create a new issue
+
+    -# Shortcut to Pipelines > Jobs
+    - if project_nav_tab? :builds
+      %li.hidden
+        = link_to project_jobs_path(@project), title: 'Jobs', class: 'shortcuts-builds' do
+          Jobs
+
+    -# Shortcut to commits page
+    - if project_nav_tab? :commits
+      %li.hidden
+        = link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do
+          Commits
+
+    -# Shortcut to issue boards
+    %li.hidden
+      = link_to 'Issue Boards', project_boards_path(@project), title: 'Issue Boards', class: 'shortcuts-issue-boards'
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 29658da7792206f2c0fc6c33b3a3325668296032..14deb46eee357cec4f8f7f11f487d07488a7f02c 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -12,30 +12,32 @@
 
     - if project_nav_tab? :files
       = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare projects/repositories tags branches releases graphs network)) do
-        = link_to project_files_path(@project), title: 'Repository',  class: 'shortcuts-tree' do
+        = link_to project_tree_path(@project), title: 'Repository',  class: 'shortcuts-tree' do
           %span
             Repository
 
     - if project_nav_tab? :container_registry
       = nav_link(controller: %w[projects/registry/repositories]) do
-        = link_to project_container_registry_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do
+        = link_to project_container_registry_index_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do
           %span
             Registry
 
     - if project_nav_tab? :issues
       = nav_link(controller: @project.default_issues_tracker? ? [:issues, :labels, :milestones, :boards] : :issues) do
-        = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues', class: 'shortcuts-issues' do
+        = link_to project_issues_path(@project), title: 'Issues', class: 'shortcuts-issues' do
           %span
             Issues
             - if @project.default_issues_tracker?
-              %span.badge.count.issue_counter= number_with_delimiter(IssuesFinder.new(current_user, project_id: @project.id).execute.opened.count)
+              %span.badge.count.issue_counter= number_with_delimiter(issuables_count_for_state(:issues, :opened, finder: IssuesFinder.new(current_user, project_id: @project.id)))
 
     - if project_nav_tab? :merge_requests
-      = nav_link(controller: @project.default_issues_tracker? ? :merge_requests : [:merge_requests, :labels, :milestones]) do
-        = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
+      - controllers = [:merge_requests, 'projects/merge_requests/conflicts']
+      - controllers.push(:merge_requests, :labels, :milestones) unless @project.default_issues_tracker?
+      = nav_link(controller: controllers) do
+        = link_to project_merge_requests_path(@project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
           %span
             Merge Requests
-            %span.badge.count.merge_counter.js-merge-counter= number_with_delimiter(MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.count)
+            %span.badge.count.merge_counter.js-merge-counter= number_with_delimiter(issuables_count_for_state(:merge_requests, :opened, finder: MergeRequestsFinder.new(current_user, project_id: @project.id)))
 
     - if project_nav_tab? :pipelines
       = nav_link(controller: [:pipelines, :builds, :environments, :artifacts]) do
@@ -51,7 +53,7 @@
 
     - if project_nav_tab? :snippets
       = nav_link(controller: :snippets) do
-        = link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets' do
+        = link_to project_snippets_path(@project), title: 'Snippets', class: 'shortcuts-snippets' do
           %span
             Snippets
 
@@ -62,7 +64,7 @@
             Settings
     - else
       = nav_link(path: %w[members#show]) do
-        = link_to namespace_project_settings_members_path(@project.namespace, @project), title: 'Settings', class: 'shortcuts-tree' do
+        = link_to project_settings_members_path(@project), title: 'Settings', class: 'shortcuts-tree' do
           %span
             Settings
 
@@ -75,18 +77,18 @@
     -# Shortcut to Repository > Graph (formerly, Network)
     - if project_nav_tab? :network
       %li.hidden
-        = link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network' do
+        = link_to project_network_path(@project, current_ref), title: 'Network', class: 'shortcuts-network' do
           Graph
 
     -# Shortcut to Repository > Charts (formerly, top-nav item "Graphs")
     - unless @project.empty_repo?
       %li.hidden
-        = link_to charts_namespace_project_graph_path(@project.namespace, @project, current_ref), title: 'Charts', class: 'shortcuts-repository-charts' do
+        = link_to charts_project_graph_path(@project, current_ref), title: 'Charts', class: 'shortcuts-repository-charts' do
           Charts
 
     -# Shortcut to Issues > New Issue
     %li.hidden
-      = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'shortcuts-new-issue' do
+      = link_to new_project_issue_path(@project), class: 'shortcuts-new-issue' do
         Create a new issue
 
     -# Shortcut to Pipelines > Jobs
@@ -103,4 +105,4 @@
 
     -# Shortcut to issue boards
     %li.hidden
-      = link_to 'Issue Boards', namespace_project_boards_path(@project.namespace, @project), title: 'Issue Boards', class: 'shortcuts-issue-boards'
+      = link_to 'Issue Boards', project_boards_path(@project), title: 'Issue Boards', class: 'shortcuts-issue-boards'
diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml
index 0ee8a57dbd4d1b4bda4f8837da0a538d81ba5080..c365839e605ddb7685fe0ec8906cf6c1d9e2b77f 100644
--- a/app/views/layouts/profile.html.haml
+++ b/app/views/layouts/profile.html.haml
@@ -1,6 +1,10 @@
 - page_title    "User Settings"
 - header_title  "User Settings", profile_path unless header_title
 - sidebar       "dashboard"
-- nav           "profile"
+- if show_new_nav?
+  - nav         "new_profile_sidebar"
+  - @new_sidebar = true
+- else
+  - nav         "profile"
 
 = render template: "layouts/application"
diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml
index 3f5b0c54e50016b4cea3fbd0ce6619833d9fdb25..99adb83cd1f18f7c5c2f035077b244d17bc3ed7b 100644
--- a/app/views/layouts/project.html.haml
+++ b/app/views/layouts/project.html.haml
@@ -1,13 +1,17 @@
 - page_title       @project.name_with_namespace
 - page_description @project.description    unless page_description
 - header_title     project_title(@project) unless header_title
-- nav              "project"
+- if show_new_nav?
+  - nav            "new_project_sidebar"
+  - @new_sidebar    = true
+- else
+  - nav            "project"
 
 - content_for :project_javascripts do
   - project = @target_project || @project
   - if current_user
     :javascript
-      window.uploads_path = "#{namespace_project_uploads_path project.namespace,project}";
+      window.uploads_path = "#{project_uploads_path(project)}";
 
 - content_for :header_content do
   .js-dropdown-menu-projects
diff --git a/app/views/notify/closed_issue_email.text.haml b/app/views/notify/closed_issue_email.text.haml
index bc12e38675f749db55257edb9e648a6fe6dcbdd4..b35d4b7502d57341cb650a2cce387c725cba12aa 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 ##{@issue.iid}: #{namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)}
+Issue ##{@issue.iid}: #{project_issue_url(@issue.project, @issue)}
diff --git a/app/views/notify/closed_merge_request_email.text.haml b/app/views/notify/closed_merge_request_email.text.haml
index d0c96b83976be60216e867b480558a4568c36a3f..c4e06cb3cb1967a359b7b0900b14e3b7745e74b3 100644
--- a/app/views/notify/closed_merge_request_email.text.haml
+++ b/app/views/notify/closed_merge_request_email.text.haml
@@ -1,6 +1,6 @@
 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)}
+Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
 
 = merge_path_description(@merge_request, 'to')
 
diff --git a/app/views/notify/issue_moved_email.html.haml b/app/views/notify/issue_moved_email.html.haml
index 40f7d61fe19f6b15ebc95f4dc275bfa839ddbf44..472c31e9a5e5fc162896b1e42a2dc33d37392830 100644
--- a/app/views/notify/issue_moved_email.html.haml
+++ b/app/views/notify/issue_moved_email.html.haml
@@ -2,5 +2,5 @@
   Issue was moved to another project.
 %p
   New issue:
-  = link_to namespace_project_issue_url(@new_project.namespace, @new_project, @new_issue) do
+  = link_to project_issue_url(@new_project, @new_issue) do
     = @new_issue.title
diff --git a/app/views/notify/issue_moved_email.text.erb b/app/views/notify/issue_moved_email.text.erb
index b3bd43c2055baf45882f66cff6e4c86cd3256dca..66ede43635b9fbc4576258eb17f8ce5366a09625 100644
--- a/app/views/notify/issue_moved_email.text.erb
+++ b/app/views/notify/issue_moved_email.text.erb
@@ -1,4 +1,4 @@
 Issue was moved to another project.
 
 New issue location:
-<%= namespace_project_issue_url(@new_project.namespace, @new_project, @new_issue) %>
+<%= project_issue_url(@new_project, @new_issue) %>
diff --git a/app/views/notify/issue_status_changed_email.text.erb b/app/views/notify/issue_status_changed_email.text.erb
index e6ab3fcde7705471517f92f92114475ab90876c4..4200881f7e8a009086be96aac46dcc8b8b41accd 100644
--- a/app/views/notify/issue_status_changed_email.text.erb
+++ b/app/views/notify/issue_status_changed_email.text.erb
@@ -1,4 +1,4 @@
 Issue was <%= @issue_status %> by <%= @updated_by.name %>
 
-Issue <%= @issue.iid %>: <%= url_for(namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)) %>
+Issue <%= @issue.iid %>: <%= url_for(project_issue_url(@issue.project, @issue)) %>
 
diff --git a/app/views/notify/merge_request_status_email.text.haml b/app/views/notify/merge_request_status_email.text.haml
index 4c9719ba7326f7c5762266da5b7973a208eedc37..ae2a29338651af37fbb7b7f893773d0a33c745a0 100644
--- a/app/views/notify/merge_request_status_email.text.haml
+++ b/app/views/notify/merge_request_status_email.text.haml
@@ -1,6 +1,6 @@
 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)}
+Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
 
 = merge_path_description(@merge_request, 'to')
 
diff --git a/app/views/notify/merged_merge_request_email.text.haml b/app/views/notify/merged_merge_request_email.text.haml
index 46c1c9dee0bb3b81b28301f4c9d689a07fc8e178..661c23bcbe20e678b6341ea30301f502fab1a8ad 100644
--- a/app/views/notify/merged_merge_request_email.text.haml
+++ b/app/views/notify/merged_merge_request_email.text.haml
@@ -1,6 +1,6 @@
 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)}
+Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
 
 = merge_path_description(@merge_request, 'to')
 
diff --git a/app/views/notify/new_issue_email.text.erb b/app/views/notify/new_issue_email.text.erb
index 13f1ac08e94548282dd93600c75eb8c51265b0a2..3c716f772967e0375410fd85e9d52486b05cdae7 100644
--- a/app/views/notify/new_issue_email.text.erb
+++ b/app/views/notify/new_issue_email.text.erb
@@ -1,6 +1,6 @@
 New Issue was created.
 
-Issue <%= @issue.iid %>: <%= url_for(namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)) %>
+Issue <%= @issue.iid %>: <%= url_for(project_issue_url(@issue.project, @issue)) %>
 Author:    <%= @issue.author_name %>
 Assignee:  <%= @issue.assignee_list %>
 
diff --git a/app/views/notify/new_mention_in_issue_email.text.erb b/app/views/notify/new_mention_in_issue_email.text.erb
index f19ac3adfc7aab9fe8d8ad628afb936ca780711d..23213106c5b295fa77ee1f26e145c1532f25382f 100644
--- a/app/views/notify/new_mention_in_issue_email.text.erb
+++ b/app/views/notify/new_mention_in_issue_email.text.erb
@@ -1,6 +1,6 @@
 You have been mentioned in an issue.
 
-Issue <%= @issue.iid %>: <%= url_for(namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)) %>
+Issue <%= @issue.iid %>: <%= url_for(project_issue_url(@issue.project, @issue)) %>
 Author:    <%= @issue.author_name %>
 Assignee:  <%= @issue.assignee_list %>
 
diff --git a/app/views/notify/new_mention_in_merge_request_email.text.erb b/app/views/notify/new_mention_in_merge_request_email.text.erb
index 5bf0282e0974e41d55f2626b3564ef2089fa7474..6fcebb22fc426c8e2897b77cafae0ea208f040e2 100644
--- a/app/views/notify/new_mention_in_merge_request_email.text.erb
+++ b/app/views/notify/new_mention_in_merge_request_email.text.erb
@@ -1,6 +1,6 @@
 You have been mentioned in Merge Request <%= @merge_request.to_reference %>
 
-<%= url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)) %>
+<%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %>
 
 <%= merge_path_description(@merge_request, 'to') %>
 Author:    <%= @merge_request.author_name %>
diff --git a/app/views/notify/new_merge_request_email.text.erb b/app/views/notify/new_merge_request_email.text.erb
index 3c8f178ac778cf34fd2e0a32191111fbe97496ca..7d98400e6fe1203cc15dbe51b279b5e7bdd8ecb2 100644
--- a/app/views/notify/new_merge_request_email.text.erb
+++ b/app/views/notify/new_merge_request_email.text.erb
@@ -1,6 +1,6 @@
 New Merge Request <%= @merge_request.to_reference %>
 
-<%= url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)) %>
+<%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %>
 
 <%= merge_path_description(@merge_request, 'to') %>
 Author:    <%= @merge_request.author_name %>
diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml
index a83faa839df44b7355591dd8ab0d46ed816b38f6..b7a609381324b3c7f94fb80230a41ba1ea0a67f9 100644
--- a/app/views/notify/pipeline_failed_email.html.haml
+++ b/app/views/notify/pipeline_failed_email.html.haml
@@ -60,7 +60,7 @@
               %tbody
                 %tr
                   %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
-                    %img.avatar{ height: "24", src: avatar_icon(commit.author || commit.author_email, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/
+                    %img.avatar{ height: "24", src: avatar_icon(commit.author || commit.author_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/
                   %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
                     - if commit.author
                       %a.muted{ href: user_url(commit.author), style: "color:#333333;text-decoration:none;" }
@@ -76,7 +76,7 @@
                 %tbody
                   %tr
                     %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
-                      %img.avatar{ height: "24", src: avatar_icon(commit.committer || commit.committer_email, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/
+                      %img.avatar{ height: "24", src: avatar_icon(commit.committer || commit.committer_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/
                     %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
                       - if commit.committer
                         %a.muted{ href: user_url(commit.committer), style: "color:#333333;text-decoration:none;" }
@@ -100,7 +100,7 @@
             triggered by
           - if @pipeline.user
             %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;padding-left:5px", width: "24" }
-              %img.avatar{ height: "24", src: avatar_icon(@pipeline.user, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/
+              %img.avatar{ height: "24", src: avatar_icon(@pipeline.user, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/
             %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;font-weight:500;line-height:1.4;vertical-align:baseline;" }
               %a.muted{ href: user_url(@pipeline.user), style: "color:#333333;text-decoration:none;" }
                 = @pipeline.user.name
diff --git a/app/views/notify/pipeline_success_email.html.haml b/app/views/notify/pipeline_success_email.html.haml
index 9c2e2a599b249ceedf68ed536c825f05c904f9e5..3f16885b8e32fef2ef8b5480c862b9c26b7e623c 100644
--- a/app/views/notify/pipeline_success_email.html.haml
+++ b/app/views/notify/pipeline_success_email.html.haml
@@ -60,7 +60,7 @@
               %tbody
                 %tr
                   %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
-                    %img.avatar{ height: "24", src: avatar_icon(commit.author || commit.author_email, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/
+                    %img.avatar{ height: "24", src: avatar_icon(commit.author || commit.author_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/
                   %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
                     - if commit.author
                       %a.muted{ href: user_url(commit.author), style: "color:#333333;text-decoration:none;" }
@@ -76,7 +76,7 @@
                 %tbody
                   %tr
                     %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
-                      %img.avatar{ height: "24", src: avatar_icon(commit.committer || commit.committer_email, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/
+                      %img.avatar{ height: "24", src: avatar_icon(commit.committer || commit.committer_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/
                     %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
                       - if commit.committer
                         %a.muted{ href: user_url(commit.committer), style: "color:#333333;text-decoration:none;" }
@@ -100,7 +100,7 @@
             triggered by
           - if @pipeline.user
             %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;padding-left:5px", width: "24" }
-              %img.avatar{ height: "24", src: avatar_icon(@pipeline.user, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/
+              %img.avatar{ height: "24", src: avatar_icon(@pipeline.user, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/
             %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;font-weight:500;line-height:1.4;vertical-align:baseline;" }
               %a.muted{ href: user_url(@pipeline.user), style: "color:#333333;text-decoration:none;" }
                 = @pipeline.user.name
diff --git a/app/views/notify/project_was_exported_email.html.haml b/app/views/notify/project_was_exported_email.html.haml
index 3def26342a13f484e617e28b7a34619dac010a8b..f0ba7827cefb2cfcd6a52618db96ae9cfe85b69b 100644
--- a/app/views/notify/project_was_exported_email.html.haml
+++ b/app/views/notify/project_was_exported_email.html.haml
@@ -2,7 +2,7 @@
   Project #{@project.name} was exported successfully.
 %p
   The project export can be downloaded from:
-  = link_to download_export_namespace_project_url(@project.namespace, @project), rel: 'nofollow', download: '' do
+  = link_to download_export_project_url(@project), rel: 'nofollow', download: '' do
     = @project.name_with_namespace + " export"
 %p
   The download link will expire in 24 hours.
diff --git a/app/views/notify/project_was_exported_email.text.erb b/app/views/notify/project_was_exported_email.text.erb
index 42c4d176876817b6c57c4902276740cf10ae3f82..cd3a1f7934f114eca0e1083a09011873f02de644 100644
--- a/app/views/notify/project_was_exported_email.text.erb
+++ b/app/views/notify/project_was_exported_email.text.erb
@@ -1,6 +1,6 @@
 Project <%= @project.name %> was exported successfully.
 
 The project export can be downloaded from:
-<%= download_export_namespace_project_url(@project.namespace, @project) %>
+<%= download_export_project_url(@project) %>
 
 The download link will expire in 24 hours.
diff --git a/app/views/notify/project_was_moved_email.html.haml b/app/views/notify/project_was_moved_email.html.haml
index 87b3ff7f0b323f07cf42be52d564519ebec73aa1..c476a39b6619de8c4e0f2128fa8a0eb26363e91e 100644
--- a/app/views/notify/project_was_moved_email.html.haml
+++ b/app/views/notify/project_was_moved_email.html.haml
@@ -2,7 +2,7 @@
   Project #{@old_path_with_namespace} was moved to another location
 %p
   The project is now located under
-  = link_to namespace_project_url(@project.namespace, @project) do
+  = link_to project_url(@project) do
     = @project.name_with_namespace
 %p
   To update the remote url in your local repository run (for ssh):
diff --git a/app/views/notify/project_was_moved_email.text.erb b/app/views/notify/project_was_moved_email.text.erb
index b2c5f71e46505e16aaa8c2c0342e46bdd5138768..7c45163e0e83badba5ac30b67fbd95a4db535984 100644
--- a/app/views/notify/project_was_moved_email.text.erb
+++ b/app/views/notify/project_was_moved_email.text.erb
@@ -1,7 +1,7 @@
 Project <%= @old_path_with_namespace %> was moved to another location
 
 The project is now located under 
-<%= namespace_project_url(@project.namespace, @project) %>
+<%= project_url(@project) %>
 
 
 To update the remote url in your local repository run (for ssh):
diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml
index 546376aeed80717119ed76d5209420933e41f3ab..5c5520f4cb8dce70a0e297e694d5dc9f54495745 100644
--- a/app/views/notify/repository_push_email.html.haml
+++ b/app/views/notify/repository_push_email.html.haml
@@ -3,7 +3,7 @@
 
 %h3
   #{@message.author_name} #{@message.action_name} #{@message.ref_type} #{@message.ref_name}
-  at #{link_to(@message.project_name_with_namespace, namespace_project_url(@message.project_namespace, @message.project))}
+  at #{link_to(@message.project_name_with_namespace, project_url(@message.project))}
 
 - if @message.compare
   - if @message.reverse_compare?
@@ -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, project_commit_url(@message.project, commit))
         %div
           %span by #{commit.author_name}
           %i at #{commit.committed_date.to_s(:iso8601)}
diff --git a/app/views/notify/resolved_all_discussions_email.text.erb b/app/views/notify/resolved_all_discussions_email.text.erb
index b0d380af8fcb07fa745080c709c53856ec1b7587..2881f3e699efe0b4ec7652c294300419946ceb54 100644
--- a/app/views/notify/resolved_all_discussions_email.text.erb
+++ b/app/views/notify/resolved_all_discussions_email.text.erb
@@ -1,3 +1,3 @@
 All discussions on Merge Request <%= @merge_request.to_reference %> were resolved by <%= @resolved_by.name %>
 
-<%= url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)) %>
+<%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %>
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index a319b18e507493a92d2c2f2a1be570c4dd55ff07..ed079ed7dfbf1306271a8f0e80ea12996cd616ee 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -1,4 +1,5 @@
 - page_title "Account"
+- @content_class = "limit-container-width" unless fluid_layout
 = render 'profiles/head'
 
 - if current_user.ldap_user?
@@ -6,13 +7,13 @@
     Some options are unavailable for LDAP accounts
 
 .row.prepend-top-default
-  .col-lg-3.profile-settings-sidebar
+  .col-lg-4.profile-settings-sidebar
     %h4.prepend-top-0
       Private Tokens
     %p
       Keep these tokens secret, anyone with access to them can interact with
       GitLab as if they were you.
-  .col-lg-9.private-tokens-reset
+  .col-lg-8.private-tokens-reset
     = render partial: 'reset_token', locals: { label: 'Private token', button_label: 'Reset private token', help_text: 'Your private token is used to access the API and Atom feeds without username/password authentication.' }
 
     = render partial: 'reset_token', locals: { label: 'RSS token', button_label: 'Reset RSS token', help_text: 'Your RSS token is used to create urls for personalized RSS feeds.' }
@@ -22,12 +23,12 @@
 
 %hr
 .row.prepend-top-default
-  .col-lg-3.profile-settings-sidebar
+  .col-lg-4.profile-settings-sidebar
     %h4.prepend-top-0
       Two-Factor Authentication
     %p
       Increase your account's security by enabling Two-Factor Authentication (2FA).
-  .col-lg-9
+  .col-lg-8
     %p
       Status: #{current_user.two_factor_enabled? ? 'Enabled' : 'Disabled'}
     - if current_user.two_factor_enabled?
@@ -43,12 +44,12 @@
 %hr
 - if button_based_providers.any?
   .row.prepend-top-default
-    .col-lg-3.profile-settings-sidebar
+    .col-lg-4.profile-settings-sidebar
       %h4.prepend-top-0
         Social sign-in
       %p
         Activate signin with one of the following services
-    .col-lg-9
+    .col-lg-8
       %label.label-light
         Connected Accounts
       %p Click on icon to activate signin with one of the following services
@@ -69,12 +70,12 @@
   %hr
 - if current_user.can_change_username?
   .row.prepend-top-default
-    .col-lg-3.profile-settings-sidebar
+    .col-lg-4.profile-settings-sidebar
       %h4.prepend-top-0.warning-title
         Change username
       %p
         Changing your username will change path to all personal projects!
-    .col-lg-9
+    .col-lg-8
       = form_for @user, url: update_username_profile_path, method: :put, html: {class: "update-username"} do |f|
         .form-group
           = f.label :username, "Path", class: "label-light"
@@ -93,10 +94,10 @@
 
 - if signup_enabled?
   .row.prepend-top-default
-    .col-lg-3.profile-settings-sidebar
+    .col-lg-4.profile-settings-sidebar
       %h4.prepend-top-0.danger-title
         Remove account
-    .col-lg-9
+    .col-lg-8
       - if @user.can_be_removed? && can?(current_user, :destroy_user, @user)
         %p
           Deleting an account has the following effects:
diff --git a/app/views/profiles/audit_log.html.haml b/app/views/profiles/audit_log.html.haml
index a24b7fd101d2bf0bf47f271d446a753cf6355815..1a392e29e2a8656fb86ee6ef0c027f695c7ee079 100644
--- a/app/views/profiles/audit_log.html.haml
+++ b/app/views/profiles/audit_log.html.haml
@@ -1,11 +1,12 @@
 - page_title "Authentication log"
+- @content_class = "limit-container-width" unless fluid_layout
 = render 'profiles/head'
 
 .row.prepend-top-default
-  .col-lg-3.profile-settings-sidebar
+  .col-lg-4.profile-settings-sidebar
     %h3.prepend-top-0
       = page_title
     %p
       This is a security log of important events involving your account.
-  .col-lg-9
+  .col-lg-8
     = render 'event_table', events: @events
diff --git a/app/views/profiles/chat_names/_chat_name.html.haml b/app/views/profiles/chat_names/_chat_name.html.haml
index 1ec1e7c70e4f08f50983295862caa817d851fb20..fe1cf802971dde2e96234f2c1284c847bedc4c29 100644
--- a/app/views/profiles/chat_names/_chat_name.html.haml
+++ b/app/views/profiles/chat_names/_chat_name.html.haml
@@ -10,7 +10,7 @@
   %td
     %strong
       - if can?(current_user, :admin_project, project)
-        = link_to service.title, edit_namespace_project_service_path(project.namespace, project, service)
+        = link_to service.title, edit_project_service_path(project, service)
       - else
         = service.title
   %td
diff --git a/app/views/profiles/chat_names/index.html.haml b/app/views/profiles/chat_names/index.html.haml
index 20cc636b2dad8300df584cbec550348d121d0125..8f7121afe02556d1b35b4d81ab4d5d60b7523ab4 100644
--- a/app/views/profiles/chat_names/index.html.haml
+++ b/app/views/profiles/chat_names/index.html.haml
@@ -1,14 +1,15 @@
 - page_title 'Chat'
+- @content_class = "limit-container-width" unless fluid_layout
 = render 'profiles/head'
 
 .row.prepend-top-default
-  .col-lg-3.profile-settings-sidebar
+  .col-lg-4.profile-settings-sidebar
     %h4.prepend-top-0
       = page_title
     %p
       You can see your Chat accounts.
 
-  .col-lg-9
+  .col-lg-8
     %h5 Active chat names (#{@chat_names.size})
 
     - if @chat_names.present?
diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml
index f5a323dbaf82b47c11850c81594934e3aeb000d9..612ecbbb96a0d14a9b39db4d50ee3b0c6351472f 100644
--- a/app/views/profiles/emails/index.html.haml
+++ b/app/views/profiles/emails/index.html.haml
@@ -1,13 +1,14 @@
 - page_title "Emails"
+- @content_class = "limit-container-width" unless fluid_layout
 = render 'profiles/head'
 
 .row.prepend-top-default
-  .col-lg-3.profile-settings-sidebar
+  .col-lg-4.profile-settings-sidebar
     %h4.prepend-top-0
       = page_title
     %p
       Control emails linked to your account
-  .col-lg-9
+  .col-lg-8
     %h4.prepend-top-0
       Add email address
     = form_for 'email', url: profile_emails_path do |f|
diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml
index 71b224a413b0231559bcd63fefb53323493b2993..5f7b41cf30e42645cbd9bb54f6414b317cfa7898 100644
--- a/app/views/profiles/keys/index.html.haml
+++ b/app/views/profiles/keys/index.html.haml
@@ -1,13 +1,14 @@
 - page_title "SSH Keys"
+- @content_class = "limit-container-width" unless fluid_layout
 = render 'profiles/head'
 
 .row.prepend-top-default
-  .col-lg-3.profile-settings-sidebar
+  .col-lg-4.profile-settings-sidebar
     %h4.prepend-top-0
       = page_title
     %p
       SSH keys allow you to establish a secure connection between your computer and GitLab.
-  .col-lg-9
+  .col-lg-8
     %h5.prepend-top-0
       Add an SSH key
     %p.profile-settings-content
diff --git a/app/views/profiles/keys/show.html.haml b/app/views/profiles/keys/show.html.haml
index 6283ceebf10faefba830ab95bb41ba704d789a4a..172c0450381c05c720081cf9dc8f4518f5ae1996 100644
--- a/app/views/profiles/keys/show.html.haml
+++ b/app/views/profiles/keys/show.html.haml
@@ -1,3 +1,4 @@
 - page_title @key.title, "SSH Keys"
+- @content_class = "limit-container-width" unless fluid_layout
 = render 'profiles/head'
 = render "key_details"
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index 51c4e8e5a73c5a252ae1a4aca6859e2fa195069e..e98fdfc7a3dd2518359a988d4a7ae8df689c9b31 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -1,4 +1,5 @@
 - page_title "Notifications"
+- @content_class = "limit-container-width" unless fluid_layout
 = render 'profiles/head'
 
 %div
@@ -10,14 +11,14 @@
 
   = hidden_field_tag :notification_type, 'global'
   .row
-    .col-lg-3.profile-settings-sidebar
+    .col-lg-4.profile-settings-sidebar
       %h4
         = page_title
       %p
         You can specify notification level per group or per project.
       %p
         By default, all projects and groups will use the global notifications setting.
-    .col-lg-9
+    .col-lg-8
       %h5
         Global notification settings
 
diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml
index 243428b690e4d2247081a9d7451d046e3e879eb4..985bb79508f4c9f8a5df1c255c7b48ac9f0965ef 100644
--- a/app/views/profiles/passwords/edit.html.haml
+++ b/app/views/profiles/passwords/edit.html.haml
@@ -1,12 +1,13 @@
 - page_title "Password"
+- @content_class = "limit-container-width" unless fluid_layout
 
 .row.prepend-top-default
-  .col-lg-3.profile-settings-sidebar
+  .col-lg-4.profile-settings-sidebar
     %h4.prepend-top-0
       = page_title
     %p
       After a successful password update, you will be redirected to the login page where you can log in with your new password.
-  .col-lg-9
+  .col-lg-8
     %h5.prepend-top-0
       Change your password
       - unless @user.password_automatically_set?
diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml
index c852107e69a1b41d93164e019d69646d8864d43c..cf750378e2578fb79fb22e0bb0fbca71e1d1b75b 100644
--- a/app/views/profiles/personal_access_tokens/index.html.haml
+++ b/app/views/profiles/personal_access_tokens/index.html.haml
@@ -1,8 +1,9 @@
 - page_title "Personal Access Tokens"
+- @content_class = "limit-container-width" unless fluid_layout
 = render 'profiles/head'
 
 .row.prepend-top-default
-  .col-lg-3.profile-settings-sidebar
+  .col-lg-4.profile-settings-sidebar
     %h4.prepend-top-0
       = page_title
     %p
@@ -11,7 +12,7 @@
       You can also use personal access tokens to authenticate against Git over HTTP.
       They are the only accepted password when you have Two-Factor Authentication (2FA) enabled.
 
-  .col-lg-9
+  .col-lg-8
 
     - if flash[:personal_access_token]
       .created-personal-access-token-container
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index 0ff19b3eab13399ccb971267eb5cbba17576f2ad..bd6020713848dc26a83c76355b42dbf095a0abd4 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -1,15 +1,16 @@
 - page_title 'Preferences'
+- @content_class = "limit-container-width" unless fluid_layout
 = render 'profiles/head'
 
 = form_for @user, url: profile_preferences_path, remote: true, method: :put, html: { class: 'row prepend-top-default js-preferences-form' } do |f|
-  .col-lg-3.profile-settings-sidebar
+  .col-lg-4.profile-settings-sidebar
     %h4.prepend-top-0
       Syntax highlighting theme
     %p
       This setting allows you to customize the appearance of the syntax.
       = succeed '.' do
         = link_to 'Learn more', help_page_path('user/profile/preferences', anchor: 'syntax-highlighting-theme'), target: '_blank'
-  .col-lg-9.syntax-theme
+  .col-lg-8.syntax-theme
     - Gitlab::ColorSchemes.each do |scheme|
       = label_tag do
         .preview= image_tag "#{scheme.css_class}-scheme-preview.png"
@@ -17,14 +18,30 @@
         = scheme.name
   .col-sm-12
     %hr
-  .col-lg-3.profile-settings-sidebar
+  .col-lg-4.profile-settings-sidebar#new-navigation
+    %h4.prepend-top-0
+      New Navigation
+    %p
+      This setting allows you to turn on or off the new upcoming navigation concept.
+  .col-lg-8.syntax-theme
+    = label_tag do
+      .preview= image_tag "old_nav.png"
+      %input.js-experiment-feature-toggle{ type: "radio", value: "false", name: "new_nav", checked: !show_new_nav? }
+      Old
+    = label_tag do
+      .preview= image_tag "new_nav.png"
+      %input.js-experiment-feature-toggle{ type: "radio", value: "true", name: "new_nav", checked: show_new_nav? }
+      New
+  .col-sm-12
+    %hr
+  .col-lg-4.profile-settings-sidebar
     %h4.prepend-top-0
       Behavior
     %p
       This setting allows you to customize the behavior of the system layout and default views.
       = succeed '.' do
         = link_to 'Learn more', help_page_path('user/profile/preferences', anchor: 'behavior'), target: '_blank'
-  .col-lg-9
+  .col-lg-8
     .form-group
       = f.label :layout, class: 'label-light' do
         Layout width
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index fcfd350f0da1acef09062f5f3bf57bfcb48f4ac5..bac75a4907503a81f255d3e31c225528c71c95cc 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -1,22 +1,23 @@
+- @content_class = "limit-container-width" unless fluid_layout
 = render 'profiles/head'
 
-= form_for @user, url: profile_path, method: :put, html: { multipart: true, class: "edit-user prepend-top-default" }, authenticity_token: true do |f|
+= bootstrap_form_for @user, url: profile_path, method: :put, html: { multipart: true, class: 'edit-user prepend-top-default' }, authenticity_token: true do |f|
   = form_errors(@user)
 
   .row
-    .col-lg-3.profile-settings-sidebar
+    .col-lg-4.profile-settings-sidebar
       %h4.prepend-top-0
         Public Avatar
       %p
         - if @user.avatar?
           You can change your avatar here
           - if gravatar_enabled?
-            or remove the current avatar to revert to #{link_to Gitlab.config.gravatar.host, "http://" + Gitlab.config.gravatar.host}
+            or remove the current avatar to revert to #{link_to Gitlab.config.gravatar.host, 'http://' + Gitlab.config.gravatar.host}
         - else
           You can upload an avatar here
           - if gravatar_enabled?
-            or change it at #{link_to Gitlab.config.gravatar.host, "http://" + Gitlab.config.gravatar.host}
-    .col-lg-9
+            or change it at #{link_to Gitlab.config.gravatar.host, 'http://' + Gitlab.config.gravatar.host}
+    .col-lg-8
       .clearfix.avatar-image.append-bottom-default
         = link_to avatar_icon(@user, 400), target: '_blank', rel: 'noopener noreferrer' do
           = image_tag avatar_icon(@user, 160), alt: '', class: 'avatar s160'
@@ -26,101 +27,67 @@
         %a.btn.js-choose-user-avatar-button
           Browse file...
         %span.avatar-file-name.prepend-left-default.js-avatar-filename No file chosen
-        = f.file_field :avatar, class: "js-user-avatar-input hidden", accept: "image/*"
+        = f.file_field_without_bootstrap :avatar, class: 'js-user-avatar-input hidden', accept: 'image/*'
       .help-block
         The maximum file size allowed is 200KB.
       - if @user.avatar?
         %hr
-        = link_to 'Remove avatar', profile_avatar_path, data: { confirm: "Avatar will be removed. Are you sure?" }, method: :delete, class: "btn btn-gray"
+        = link_to 'Remove avatar', profile_avatar_path, data: { confirm: 'Avatar will be removed. Are you sure?' }, method: :delete, class: 'btn btn-gray'
   %hr
   .row
-    .col-lg-3.profile-settings-sidebar
+    .col-lg-4.profile-settings-sidebar
       %h4.prepend-top-0
         Main settings
       %p
         This information will appear on your profile.
         - if current_user.ldap_user?
           Some options are unavailable for LDAP accounts
-    .col-lg-9
-      .form-group
-        = f.label :name, class: "label-light"
-        = f.text_field :name, class: "form-control", required: true
-        %span.help-block Enter your name, so people you know can recognize you.
+    .col-lg-8
+      .row
+        = f.text_field :name,  required: true, wrapper: { class: 'col-md-9' },
+          help: 'Enter your name, so people you know can recognize you.'
+        = f.text_field :id, readonly: true, label: 'User ID', wrapper: { class: 'col-md-3' }
 
-      .form-group
-        = f.label :email, class: "label-light"
-        - if @user.external_email?
-          = f.text_field :email, class: "form-control", required: true, readonly: true
-          %span.help-block.light
-            Your email address was automatically set based on your #{email_provider_label} account.
-        - else
-          - if @user.temp_oauth_email?
-            = f.text_field :email, class: "form-control", required: true, value: nil
-          - else
-            = f.text_field :email, class: "form-control", required: true
-          - if @user.unconfirmed_email.present?
-            %span.help-block
-              Please click the link in the confirmation email before continuing. It was sent to
-              = succeed "." do
-                %strong= @user.unconfirmed_email
-              %p
-              = link_to "Resend confirmation e-mail", user_confirmation_path(user: { email: @user.unconfirmed_email }), method: :post
-
-          - else
-            %span.help-block We also use email for avatar detection if no avatar is uploaded.
-      .form-group
-        = f.label :public_email, class: "label-light"
-        = f.select :public_email, options_for_select(@user.all_emails, selected: @user.public_email), { include_blank: 'Do not show on profile' }, class: "select2"
-        %span.help-block This email will be displayed on your public profile.
-      .form-group
-        = f.label :preferred_language, class: "label-light"
-        = f.select :preferred_language, Gitlab::I18n::AVAILABLE_LANGUAGES.map { |value, label| [label, value] },
-          {}, class: "select2"
-        %span.help-block This feature is experimental and translations are not complete yet.
-      .form-group
-        = f.label :skype, class: "label-light"
-        = f.text_field :skype, class: "form-control"
-      .form-group
-        = f.label :linkedin, class: "label-light"
-        = f.text_field :linkedin, class: "form-control"
-      .form-group
-        = f.label :twitter, class: "label-light"
-        = f.text_field :twitter, class: "form-control"
-      .form-group
-        = f.label :website_url, 'Website', class: "label-light"
-        = f.text_field :website_url, class: "form-control"
-      .form-group
-        = f.label :location, 'Location', class: "label-light"
-        = f.text_field :location, class: "form-control"
-      .form-group
-        = f.label :organization, 'Organization', class: "label-light"
-        = f.text_field :organization, class: "form-control"
-      .form-group
-        = f.label :bio, class: "label-light"
-        = f.text_area :bio, rows: 4, class: "form-control", maxlength: 250
-        %span.help-block Tell us about yourself in fewer than 250 characters.
+      - if @user.external_email?
+        = f.text_field :email, required: true, readonly: true, help: "Your email address was automatically set based on your #{email_provider_label} account."
+      - else
+        = f.text_field :email, required: true, value: (@user.email unless @user.temp_oauth_email?),
+          help: user_email_help_text(@user)
+      = f.select :public_email, options_for_select(@user.all_emails, selected: @user.public_email),
+        { help: 'This email will be displayed on your public profile.', include_blank: 'Do not show on profile' },
+        control_class: 'select2'
+      = f.select :preferred_language, Gitlab::I18n::AVAILABLE_LANGUAGES.map { |value, label| [label, value] },
+        { help: 'This feature is experimental and translations are not complete yet.' },
+        control_class: 'select2'
+      = f.text_field :skype
+      = f.text_field :linkedin
+      = f.text_field :twitter
+      = f.text_field :website_url, label: 'Website'
+      = f.text_field :location
+      = f.text_field :organization
+      = f.text_area :bio, rows: 4, maxlength: 250, help: 'Tell us about yourself in fewer than 250 characters.'
       .prepend-top-default.append-bottom-default
-        = f.submit 'Update profile settings', class: "btn btn-success"
-        = link_to "Cancel", user_path(current_user), class: "btn btn-cancel"
+        = f.submit 'Update profile settings', class: 'btn btn-success'
+        = link_to 'Cancel', user_path(current_user), class: 'btn btn-cancel'
 
 .modal.modal-profile-crop
   .modal-dialog
     .modal-content
       .modal-header
-        %button.close{ :type => "button", :'data-dismiss' => "modal" }
+        %button.close{ type: 'button', 'data-dismiss': 'modal' }
           %span
             &times;
         %h4.modal-title
           Position and size your new avatar
       .modal-body
         .profile-crop-image-container
-          %img.modal-profile-crop-image{ alt: "Avatar cropper" }
+          %img.modal-profile-crop-image{ alt: 'Avatar cropper' }
         .crop-controls
           .btn-group
-            %button.btn.btn-primary{ data: { method: "zoom", option: "0.1" } }
+            %button.btn.btn-primary{ data: { method: 'zoom', option: '0.1' } }
               %span.fa.fa-search-plus
-            %button.btn.btn-primary{ data: { method: "zoom", option: "-0.1" } }
+            %button.btn.btn-primary{ data: { method: 'zoom', option: '-0.1' } }
               %span.fa.fa-search-minus
       .modal-footer
-        %button.btn.btn-primary.js-upload-user-avatar{ :type => "button" }
+        %button.btn.btn-primary.js-upload-user-avatar{ type: 'button' }
           Set new profile picture
diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml
index 0ff05098cd7e0c74b48545649ac9d34006d9adca..67792de3870b052fa035b00c79e0736c8ba3afd2 100644
--- a/app/views/profiles/two_factor_auths/show.html.haml
+++ b/app/views/profiles/two_factor_auths/show.html.haml
@@ -1,5 +1,6 @@
 - page_title 'Two-Factor Authentication', 'Account'
 - header_title "Two-Factor Authentication", profile_two_factor_auth_path
+- @content_class = "limit-container-width" unless fluid_layout
 = render 'profiles/head'
 
 - if inject_u2f_api?
@@ -7,12 +8,12 @@
     = page_specific_javascript_bundle_tag('u2f')
 
 .row.prepend-top-default
-  .col-lg-3
+  .col-lg-4
     %h4.prepend-top-0
       Register Two-Factor Authentication App
     %p
       Use an app on your mobile device to enable two-factor authentication (2FA).
-  .col-lg-9
+  .col-lg-8
     - if current_user.two_factor_otp_enabled?
       = icon "check inverse", base: "circle", class: "text-success", text: "You've already enabled two-factor authentication using mobile authenticator applications. You can disable it from your account settings page."
     - else
@@ -20,9 +21,9 @@
         Download the Google Authenticator application from App Store or Google Play Store and scan this code.
         More information is available in the #{link_to('documentation', help_page_path('profile/two_factor_authentication'))}.
       .row.append-bottom-10
-        .col-md-3
+        .col-md-4
           = raw @qr_code
-        .col-md-9
+        .col-md-8
           .account-well
             %p.prepend-top-0.append-bottom-0
               Can't scan the code?
@@ -50,7 +51,7 @@
 
 .row.prepend-top-default
 
-  .col-lg-3
+  .col-lg-4
     %h4.prepend-top-0
       Register Universal Two-Factor (U2F) Device
     %p
@@ -59,7 +60,7 @@
       As U2F devices are only supported by a few browsers, we require that you set up a
       two-factor authentication app before a U2F device. That way you'll always be able to
       log in - even when you're using an unsupported browser.
-  .col-lg-9
+  .col-lg-8
     - if @u2f_registration.errors.present?
       = form_errors(@u2f_registration)
     = render "u2f/register"
diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml
index 10f581d751b690aa6ac123b420977de4fae4c198..ecc966ed4534f472bb861f7e7be94445a64e1058 100644
--- a/app/views/projects/_activity.html.haml
+++ b/app/views/projects/_activity.html.haml
@@ -1,7 +1,7 @@
 %div{ class: container_class }
   .nav-block.activity-filter-block.activities
     .controls
-      = link_to namespace_project_path(@project.namespace, @project, rss_url_options), title: "Subscribe", class: 'btn rss-btn has-tooltip' do
+      = link_to project_path(@project, rss_url_options), title: "Subscribe", class: 'btn rss-btn has-tooltip' do
         = icon('rss')
 
     = render 'shared/event_filter'
diff --git a/app/views/projects/_find_file_link.html.haml b/app/views/projects/_find_file_link.html.haml
index c748ccf65e6593cd8a40bf0d377dd3a67f774cbb..da1b2d7f9b64b98e0b7030f9039cedbc18711195 100644
--- a/app/views/projects/_find_file_link.html.haml
+++ b/app/views/projects/_find_file_link.html.haml
@@ -1,3 +1,3 @@
-= link_to namespace_project_find_file_path(@project.namespace, @project, @ref), class: 'btn btn-grouped shortcuts-find-file', rel: 'nofollow' do
+= link_to project_find_file_path(@project, @ref), class: 'btn shortcuts-find-file', rel: 'nofollow' do
   = icon('search')
   %span= _('Find file')
diff --git a/app/views/projects/_last_push.html.haml b/app/views/projects/_last_push.html.haml
index f1ef50d2de24fdc085098d365b1e8f813a926619..1a71bfca2e244dc306f4989f494cd8e6f6e3c6bf 100644
--- a/app/views/projects/_last_push.html.haml
+++ b/app/views/projects/_last_push.html.haml
@@ -5,7 +5,7 @@
       .event-last-push-text
         %span You pushed to
         %strong
-          = link_to event.ref_name, namespace_project_commits_path(event.project.namespace, event.project, event.ref_name), class: 'ref-name'
+          = link_to event.ref_name, project_commits_path(event.project, event.ref_name), class: 'ref-name'
 
         - if event.project != @project
           %span at
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index 07445434cf3f579c822351e782f194c0416bb970..d0698285f84919758a38ec5299518a347fdcf704 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -9,6 +9,12 @@
       %li
         %a.js-md-preview-button{ href: "#md-preview-holder", tabindex: -1 }
           Preview
+
+      - if defined?(@issue) && @issue.confidential?
+        %li.confidential-issue-warning
+          = icon('warning')
+          %span This is a confidential issue. Your comment will not be visible to the public.
+
       %li.pull-right
         .toolbar-group
           = markdown_toolbar_button({ icon: "bold fw", data: { "md-tag" => "**" }, title: "Add bold text" })
diff --git a/app/views/projects/_visibility_select.html.haml b/app/views/projects/_visibility_select.html.haml
index 65fc0a36ca9e33923e360d2dfbda6ab4f0c3a4fc..4026b9e3c46d6f1e0b6b8a9b5305ed80959013e2 100644
--- a/app/views/projects/_visibility_select.html.haml
+++ b/app/views/projects/_visibility_select.html.haml
@@ -1,5 +1,7 @@
 - if can_change_visibility_level?(@project, current_user)
-  = form.select(model_method, visibility_select_options(@project, selected_level), {}, class: 'form-control visibility-select')
+  .select-wrapper
+    = form.select(model_method, visibility_select_options(@project, selected_level), {}, class: 'form-control visibility-select select-control')
+    = icon('chevron-down')
 - else
   .info.js-locked{ data: { help_block: visibility_level_description(@project.visibility_level, @project) } }
     = visibility_level_icon(@project.visibility_level)
diff --git a/app/views/projects/_wiki.html.haml b/app/views/projects/_wiki.html.haml
index 2bab22e125d96de5e4e6a71b18c69ff18966403d..a56c3503c776384aa080418413c51c4a3c001ebc 100644
--- a/app/views/projects/_wiki.html.haml
+++ b/app/views/projects/_wiki.html.haml
@@ -14,5 +14,5 @@
           Add a homepage to your wiki that contains information about your project
         %p
           We recommend you
-          = link_to "add a homepage", namespace_project_wiki_path(@project.namespace, @project, :home)
+          = link_to "add a homepage", project_wiki_path(@project, :home)
           to your project's wiki and GitLab will show it here instead of this message.
diff --git a/app/views/projects/_zen.html.haml b/app/views/projects/_zen.html.haml
index 3b3d08ddd3c90477608280f2cbf0864fb32a245e..afc40ca4eabd06699f0ead75fd2ce52217e9013e 100644
--- a/app/views/projects/_zen.html.haml
+++ b/app/views/projects/_zen.html.haml
@@ -1,10 +1,15 @@
 - @gfm_form = true
 - current_text ||= nil
-- supports_slash_commands = local_assigns.fetch(:supports_slash_commands, false)
+- supports_autocomplete = local_assigns.fetch(:supports_autocomplete, true)
+- supports_quick_actions = local_assigns.fetch(:supports_quick_actions, false)
 .zen-backdrop
   - classes << ' js-gfm-input js-autosize markdown-area'
   - if defined?(f) && f
-    = f.text_area attr, class: classes, placeholder: placeholder, data: { supports_slash_commands: supports_slash_commands }
+    = f.text_area attr,
+      class: classes,
+      placeholder: placeholder,
+      data: { supports_quick_actions: supports_quick_actions,
+        supports_autocomplete: supports_autocomplete }
   - else
     = text_area_tag attr, current_text, class: classes, placeholder: placeholder
   %a.zen-control.zen-control-leave.js-zen-leave{ href: "#" }
diff --git a/app/views/projects/artifacts/_tree_directory.html.haml b/app/views/projects/artifacts/_tree_directory.html.haml
index e2966ec33c2ffa8d68d46471126cbabe3870e1a2..03be6f15313d09b7e29f6fdd9da487860f879318 100644
--- a/app/views/projects/artifacts/_tree_directory.html.haml
+++ b/app/views/projects/artifacts/_tree_directory.html.haml
@@ -1,4 +1,4 @@
-- path_to_directory = browse_namespace_project_job_artifacts_path(@project.namespace, @project, @build, path: directory.path)
+- path_to_directory = browse_project_job_artifacts_path(@project, @build, path: directory.path)
 
 %tr.tree-item{ 'data-link' => path_to_directory }
   %td.tree-item-file-name
diff --git a/app/views/projects/artifacts/_tree_file.html.haml b/app/views/projects/artifacts/_tree_file.html.haml
index ea0b43b85cfc4b7850cbd0fdd2d517a9e6133101..8edb9be049a18c7a7496365a286f8e8c19a00641 100644
--- a/app/views/projects/artifacts/_tree_file.html.haml
+++ b/app/views/projects/artifacts/_tree_file.html.haml
@@ -1,4 +1,4 @@
-- path_to_file = file_namespace_project_job_artifacts_path(@project.namespace, @project, @build, path: file.path)
+- path_to_file = file_project_job_artifacts_path(@project, @build, path: file.path)
 
 %tr.tree-item{ 'data-link' => path_to_file }
   - blob = file.blob
diff --git a/app/views/projects/artifacts/browse.html.haml b/app/views/projects/artifacts/browse.html.haml
index 961c805dc7c9ef498d8ba4bc62ff3d2806dcfc0b..576e5b385afb4902585768aa29faa80b4221676f 100644
--- a/app/views/projects/artifacts/browse.html.haml
+++ b/app/views/projects/artifacts/browse.html.haml
@@ -6,17 +6,17 @@
 .tree-holder
   .nav-block
     .tree-controls
-      = link_to download_namespace_project_job_artifacts_path(@project.namespace, @project, @build),
+      = link_to download_project_job_artifacts_path(@project, @build),
         rel: 'nofollow', download: '', class: 'btn btn-default download' do
         = icon('download')
         Download artifacts archive
 
     %ul.breadcrumb.repo-breadcrumb
       %li
-        = link_to 'Artifacts', browse_namespace_project_job_artifacts_path(@project.namespace, @project, @build)
+        = link_to 'Artifacts', browse_project_job_artifacts_path(@project, @build)
       - path_breadcrumbs do |title, path|
         %li
-          = link_to truncate(title, length: 40), browse_namespace_project_job_artifacts_path(@project.namespace, @project, @build, path)
+          = link_to truncate(title, length: 40), browse_project_job_artifacts_path(@project, @build, path)
 
   .tree-content-holder
     %table.table.tree-table
diff --git a/app/views/projects/artifacts/file.html.haml b/app/views/projects/artifacts/file.html.haml
index b25c7c95196d101d41bec12d60ec266789a586ee..18e86ac5a92a2ec630796263040227e040c207ef 100644
--- a/app/views/projects/artifacts/file.html.haml
+++ b/app/views/projects/artifacts/file.html.haml
@@ -7,15 +7,15 @@
   .nav-block
     %ul.breadcrumb.repo-breadcrumb
       %li
-        = link_to 'Artifacts', browse_namespace_project_job_artifacts_path(@project.namespace, @project, @build)
+        = link_to 'Artifacts', browse_project_job_artifacts_path(@project, @build)
       - path_breadcrumbs do |title, path|
         - title = truncate(title, length: 40)
         %li
           - if path == @path
-            = link_to file_namespace_project_job_artifacts_path(@project.namespace, @project, @build, path) do
+            = link_to file_project_job_artifacts_path(@project, @build, path) do
               %strong= title
           - else
-            = link_to title, browse_namespace_project_job_artifacts_path(@project.namespace, @project, @build, path)
+            = link_to title, browse_project_job_artifacts_path(@project, @build, path)
 
 
   %article.file-holder
diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml
index ce937ee184200117e8ccdb404193de4539768613..f11afe8fc224b493d9bb8fb77dbf514543d16d81 100644
--- a/app/views/projects/blame/show.html.haml
+++ b/app/views/projects/blame/show.html.haml
@@ -1,6 +1,6 @@
 - @no_container = true
 - project_duration = age_map_duration(@blame_groups, @project)
-- page_title "Annotate", @blob.path, @ref
+- page_title "Blame", @blob.path, @ref
 = render "projects/commits/head"
 
 %div{ class: container_class }
@@ -22,9 +22,9 @@
                   = author_avatar(commit, size: 36)
                   .commit-row-title
                     %strong
-                      = link_to_gfm truncate(commit.title, length: 35), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "cdark"
+                      = link_to_gfm truncate(commit.title, length: 35), project_commit_path(@project, commit.id), class: "cdark"
                     .pull-right
-                      = link_to commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit), class: "commit-sha"
+                      = link_to commit.short_id, project_commit_path(@project, commit), class: "commit-sha"
                     &nbsp;
                   .light
                     = commit_author_link(commit, avatar: false)
diff --git a/app/views/projects/blob/_breadcrumb.html.haml b/app/views/projects/blob/_breadcrumb.html.haml
index 0ad9f258e4849e00123fca383a3dec04d02dd15b..1c148de9678ca906d5fc2d5d9a1fe6ce0ffb71e6 100644
--- a/app/views/projects/blob/_breadcrumb.html.haml
+++ b/app/views/projects/blob/_breadcrumb.html.haml
@@ -1,36 +1,37 @@
 - blame = local_assigns.fetch(:blame, false)
 .nav-block
+  .tree-ref-container
+    .tree-ref-holder
+      = render 'shared/ref_switcher', destination: 'blob', path: @path
+
+    %ul.breadcrumb.repo-breadcrumb
+      %li
+        = link_to project_tree_path(@project, @ref) do
+          = @project.path
+      - path_breadcrumbs do |title, path|
+        - title = truncate(title, length: 40)
+        %li
+          - if path == @path
+            = link_to project_blob_path(@project, tree_join(@ref, path)) do
+              %strong= title
+          - else
+            = link_to title, project_tree_path(@project, tree_join(@ref, path))
+
   .tree-controls
     = render 'projects/find_file_link'
 
-    .btn-group.prepend-left-10{ role: "group" }<
+    .btn-group{ role: "group" }<
       -# only show normal/blame view links for text files
       - if blob.readable_text?
         - if blame
-          = link_to 'Normal view', namespace_project_blob_path(@project.namespace, @project, @id),
+          = link_to 'Normal view', project_blob_path(@project, @id),
               class: 'btn'
         - else
-          = link_to 'Annotate', namespace_project_blame_path(@project.namespace, @project, @id),
+          = link_to 'Blame', project_blame_path(@project, @id),
               class: 'btn js-blob-blame-link' unless blob.empty?
 
-      = link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id),
+      = link_to 'History', project_commits_path(@project, @id),
           class: 'btn'
 
-      = link_to 'Permalink', namespace_project_blob_path(@project.namespace, @project,
+      = link_to 'Permalink', project_blob_path(@project,
           tree_join(@commit.sha, @path)), class: 'btn js-data-file-blob-permalink-url'
-
-  .tree-ref-holder
-    = render 'shared/ref_switcher', destination: 'blob', path: @path
-
-  %ul.breadcrumb.repo-breadcrumb
-    %li
-      = link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
-        = @project.path
-    - path_breadcrumbs do |title, path|
-      - title = truncate(title, length: 40)
-      %li
-        - if path == @path
-          = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@ref, path)) do
-            %strong= title
-        - else
-          = link_to title, namespace_project_tree_path(@project.namespace, @project, tree_join(@ref, path))
diff --git a/app/views/projects/blob/_new_dir.html.haml b/app/views/projects/blob/_new_dir.html.haml
index 40978583e8b2337490107fc709c15cbf9818dca7..b2959ef6d31f4f0b770fc1d970a29a8ce0a51c52 100644
--- a/app/views/projects/blob/_new_dir.html.haml
+++ b/app/views/projects/blob/_new_dir.html.haml
@@ -5,7 +5,7 @@
         %a.close{ href: "#", "data-dismiss" => "modal" } ×
         %h3.page-title= _('Create New Directory')
       .modal-body
-        = form_tag namespace_project_create_dir_path(@project.namespace, @project, @id), method: :post, remote: false, class: 'form-horizontal js-create-dir-form js-quick-submit js-requires-input' do
+        = form_tag project_create_dir_path(@project, @id), method: :post, remote: false, class: 'form-horizontal js-create-dir-form js-quick-submit js-requires-input' do
           .form-group
             = label_tag :dir_name, _('Directory name'), class: 'control-label'
             .col-sm-10
diff --git a/app/views/projects/blob/_remove.html.haml b/app/views/projects/blob/_remove.html.haml
index c8ca040621384d2a959fcb06a97311338da109fe..6a4a657fa8caf140f9c56eee9c6b8b5b81753b8c 100644
--- a/app/views/projects/blob/_remove.html.haml
+++ b/app/views/projects/blob/_remove.html.haml
@@ -6,7 +6,7 @@
         %h3.page-title Delete #{@blob.name}
 
       .modal-body
-        = form_tag namespace_project_blob_path(@project.namespace, @project, @id), method: :delete, class: 'form-horizontal js-delete-blob-form js-quick-submit js-requires-input' do
+        = form_tag project_blob_path(@project, @id), method: :delete, class: 'form-horizontal js-delete-blob-form js-quick-submit js-requires-input' do
           = render 'shared/new_commit_form', placeholder: "Delete #{@blob.name}"
 
           .form-group
diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml
index e14885f264b615b661759cafd4e25d14242f36dc..32dbc1b3417665dfa322f54557aebcb8049fecfe 100644
--- a/app/views/projects/blob/_upload.html.haml
+++ b/app/views/projects/blob/_upload.html.haml
@@ -9,8 +9,10 @@
           .dropzone
             .dropzone-previews.blob-upload-dropzone-previews
               %p.dz-message.light
-                Attach a file by drag &amp; drop or
-                = link_to 'click to upload', '#', class: "markdown-selector"
+                - upload_link = link_to s_('UploadLink|click to upload'), '#', class: "markdown-selector"
+                - dropzone_text = _('Attach a file by drag &amp; drop or %{upload_link}') % { upload_link: upload_link }
+                #{ dropzone_text.html_safe }
+
           %br
           .dropzone-alerts.alert.alert-danger.data{ style: "display:none" }
 
@@ -18,7 +20,7 @@
 
           .form-actions
             = button_tag button_title, class: 'btn btn-small btn-create btn-upload-file', id: 'submit-all'
-            = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
+            = link_to _("Cancel"), '#', class: "btn btn-cancel", "data-dismiss" => "modal"
 
             - unless can?(current_user, :push_code, @project)
               .inline.prepend-left-10
diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml
index 4af624611514dcdd92686667090de4ca6a8e2192..f8cb612a2b4bca6f6acd67599f38d6c8d6d993cf 100644
--- a/app/views/projects/blob/edit.html.haml
+++ b/app/views/projects/blob/edit.html.haml
@@ -9,7 +9,7 @@
   - if @conflict
     .alert.alert-danger
       Someone edited the file the same time you did. Please check out
-      = link_to "the file", namespace_project_blob_path(@project.namespace, @project, tree_join(@branch_name, @file_path)), target: "_blank", rel: 'noopener noreferrer'
+      = link_to "the file", project_blob_path(@project, tree_join(@branch_name, @file_path)), target: "_blank", rel: 'noopener noreferrer'
       and make sure your changes will not unintentionally remove theirs.
   .editor-title-row
     %h3.page-title.blob-edit-page-title
@@ -22,13 +22,13 @@
           Write
 
       %li
-        = link_to '#preview', 'data-preview-url' => namespace_project_preview_blob_path(@project.namespace, @project, @id) do
+        = link_to '#preview', 'data-preview-url' => project_preview_blob_path(@project, @id) do
           = editing_preview_title(@blob.name)
 
-    = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-quick-submit js-requires-input js-edit-blob-form', data: blob_editor_paths) do
+    = form_tag(project_update_blob_path(@project, @id), method: :put, class: 'form-horizontal js-quick-submit js-requires-input js-edit-blob-form', data: blob_editor_paths) do
       = render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data
       = render 'shared/new_commit_form', placeholder: "Update #{@blob.name}"
       = hidden_field_tag 'last_commit_sha', @last_commit_sha
       = hidden_field_tag 'content', '', id: "file-content"
       = hidden_field_tag 'from_merge_request_iid', params[:from_merge_request_iid]
-      = render 'projects/commit_button', ref: @ref, cancel_path: namespace_project_blob_path(@project.namespace, @project, @id)
+      = render 'projects/commit_button', ref: @ref, cancel_path: project_blob_path(@project, @id)
diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml
index 2afb909572a425f4b5231da8851c43db5e6aafad..8620a470041eb059c178f6fd1e4f1c62f55db826 100644
--- a/app/views/projects/blob/new.html.haml
+++ b/app/views/projects/blob/new.html.haml
@@ -7,10 +7,10 @@
     New file
   = render 'template_selectors'
 .file-editor
-  = form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal js-edit-blob-form js-new-blob-form js-quick-submit js-requires-input', data: blob_editor_paths) do
+  = form_tag(project_create_blob_path(@project, @id), method: :post, class: 'form-horizontal js-edit-blob-form js-new-blob-form js-quick-submit js-requires-input', data: blob_editor_paths) do
     = render 'projects/blob/editor', ref: @ref
     = render 'shared/new_commit_form', placeholder: "Add new file"
 
     = hidden_field_tag 'content', '', id: 'file-content'
     = render 'projects/commit_button', ref: @ref,
-              cancel_path: namespace_project_tree_path(@project.namespace, @project, @id)
+              cancel_path: project_tree_path(@project, @id)
diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml
index 41f75a491a54e3628c3d7438d0d3de6c0da7c87a..6e2ae4717cdb918ddff4e32fa8dbb42e2d1b7d0a 100644
--- a/app/views/projects/blob/show.html.haml
+++ b/app/views/projects/blob/show.html.haml
@@ -16,4 +16,4 @@
     = render 'projects/blob/remove'
 
     - title = "Replace #{@blob.name}"
-    = render 'projects/blob/upload', title: title, placeholder: title, button_title: 'Replace file', form_path: namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put
+    = render 'projects/blob/upload', title: title, placeholder: title, button_title: 'Replace file', form_path: project_update_blob_path(@project, @id), method: :put
diff --git a/app/views/projects/blob/viewers/_changelog.html.haml b/app/views/projects/blob/viewers/_changelog.html.haml
index 53921e63b5f6254f740a766af54f01c7a3d82d09..46e3e7f798a7db0b51e872033dce2d8fc5c89c23 100644
--- a/app/views/projects/blob/viewers/_changelog.html.haml
+++ b/app/views/projects/blob/viewers/_changelog.html.haml
@@ -1,4 +1,4 @@
 = icon('history fw')
 = succeed '.' do
   To find the state of this project's repository at the time of any of these versions, check out
-  = link_to "the tags", namespace_project_tags_path(viewer.project.namespace, viewer.project)
+  = link_to "the tags", project_tags_path(viewer.project)
diff --git a/app/views/projects/blob/viewers/_readme.html.haml b/app/views/projects/blob/viewers/_readme.html.haml
index 334b33faf486e5964350a22c7f459e7d27141ac1..507f44d47452d7fbeb0a2ad96f6906d062359a4f 100644
--- a/app/views/projects/blob/viewers/_readme.html.haml
+++ b/app/views/projects/blob/viewers/_readme.html.haml
@@ -1,4 +1,4 @@
 = icon('info-circle fw')
 = succeed '.' do
   To learn more about this project, read
-  = link_to "the wiki", namespace_project_wikis_path(viewer.project.namespace, viewer.project)
+  = link_to "the wiki", project_wikis_path(viewer.project)
diff --git a/app/views/projects/boards/_show.html.haml b/app/views/projects/boards/_show.html.haml
index 6684ecfce8106ff9f4ceb9a44755564b07e151bf..07272ea2df1869f1eb66183f620b11949384e738 100644
--- a/app/views/projects/boards/_show.html.haml
+++ b/app/views/projects/boards/_show.html.haml
@@ -2,6 +2,10 @@
 - @content_class = "issue-boards-content"
 - page_title "Boards"
 
+- if show_new_nav?
+  - content_for :sub_title_before do
+    %li= link_to "Issues", project_issues_path(@project)
+
 - content_for :page_specific_javascripts do
   = webpack_bundle_tag 'common_vue'
   = webpack_bundle_tag 'filtered_search'
@@ -30,7 +34,7 @@
       ":key" => "_uid" }
   = render "projects/boards/components/sidebar"
   %board-add-issues-modal{ "blank-state-image" => render('shared/empty_states/icons/issues.svg'),
-    "new-issue-path" => new_namespace_project_issue_path(@project.namespace, @project),
+    "new-issue-path" => new_project_issue_path(@project),
     "milestone-path" => milestones_filter_dropdown_path,
     "label-path" => labels_filter_path,
     ":issue-link-base" => "issueLinkBase",
diff --git a/app/views/projects/boards/components/_sidebar.html.haml b/app/views/projects/boards/components/_sidebar.html.haml
index 24d76da6f0632a26c84d9b04f95752aec4ace5f1..09d70f658a3a9463d28c27b63c0ff20364ba23c9 100644
--- a/app/views/projects/boards/components/_sidebar.html.haml
+++ b/app/views/projects/boards/components/_sidebar.html.haml
@@ -23,4 +23,5 @@
           = render "projects/boards/components/sidebar/labels"
           = render "projects/boards/components/sidebar/notifications"
           %remove-btn{ ":issue" => "issue",
-            ":list" => "list" }
+            ":list" => "list",
+            "v-if" => "canRemove" }
diff --git a/app/views/projects/boards/components/sidebar/_assignee.html.haml b/app/views/projects/boards/components/sidebar/_assignee.html.haml
index e8db868f49b2ef66e8a12a85c9c5898dffb3e972..8d957613be15e68f4d47415d038bafb78de5b077 100644
--- a/app/views/projects/boards/components/sidebar/_assignee.html.haml
+++ b/app/views/projects/boards/components/sidebar/_assignee.html.haml
@@ -19,10 +19,11 @@
         ":data-name" => "assignee.name",
         ":data-username" => "assignee.username" }
       .dropdown
-        %button.dropdown-menu-toggle.js-user-search.js-author-search.js-multiselect.js-save-user-data.js-issue-board-sidebar{ type: "button", ref: "assigneeDropdown", data: { toggle: "dropdown", field_name: "issue[assignee_ids][]", first_user: (current_user.username if current_user), current_user: "true", project_id: @project.id, null_user: "true", multi_select: "true", 'max-select' => 1, dropdown: { header: 'Assignee' } },
+        - dropdown_options = issue_assignees_dropdown_options
+        %button.dropdown-menu-toggle.js-user-search.js-author-search.js-multiselect.js-save-user-data.js-issue-board-sidebar{ type: 'button', ref: 'assigneeDropdown', data: { toggle: 'dropdown', field_name: 'issue[assignee_ids][]', first_user: current_user&.username, current_user: 'true', project_id: @project.id, null_user: 'true', multi_select: 'true', 'dropdown-header': dropdown_options[:data][:'dropdown-header'], 'max-select': dropdown_options[:data][:'max-select'] },
           ":data-issuable-id" => "issue.id",
-          ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" }
-          Select assignee
+          ":data-issue-update" => "'#{project_issues_path(@project)}/' + issue.id + '.json'" }
+          = dropdown_options[:title]
           = icon("chevron-down")
         .dropdown-menu.dropdown-select.dropdown-menu-user.dropdown-menu-selectable.dropdown-menu-author
           = dropdown_title("Assign to")
diff --git a/app/views/projects/boards/components/sidebar/_due_date.html.haml b/app/views/projects/boards/components/sidebar/_due_date.html.haml
index 1a3b88e28c577b3678b63da6b8eb78f5c3048632..f44a9d49a54ab67bdf5560aa4342da639fbd55fa 100644
--- a/app/views/projects/boards/components/sidebar/_due_date.html.haml
+++ b/app/views/projects/boards/components/sidebar/_due_date.html.haml
@@ -23,7 +23,7 @@
       .dropdown
         %button.dropdown-menu-toggle.js-due-date-select.js-issue-boards-due-date{ type: 'button',
           data: { toggle: 'dropdown', field_name: "issue[due_date]", ability_name: "issue" },
-          ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" }
+          ":data-issue-update" => "'#{project_issues_path(@project)}/' + issue.id + '.json'" }
           %span.dropdown-toggle-text Due date
           = icon('chevron-down')
         .dropdown-menu.dropdown-menu-due-date
diff --git a/app/views/projects/boards/components/sidebar/_labels.html.haml b/app/views/projects/boards/components/sidebar/_labels.html.haml
index bee0f3dd065f700f9c5a17440bf02e75a791786d..7d0c35fe183b71a7631ff0ef683c34839671bb91 100644
--- a/app/views/projects/boards/components/sidebar/_labels.html.haml
+++ b/app/views/projects/boards/components/sidebar/_labels.html.haml
@@ -19,8 +19,8 @@
         ":value" => "label.id" }
       .dropdown
         %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-issue-board-sidebar{ type: "button",
-          data: { toggle: "dropdown", field_name: "issue[label_names][]", show_no: "true", show_any: "true", project_id: @project.id, labels: namespace_project_labels_path(@project.namespace, @project, :json), namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path) },
-          ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" }
+          data: { toggle: "dropdown", field_name: "issue[label_names][]", show_no: "true", show_any: "true", project_id: @project.id, labels: project_labels_path(@project, :json), namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path) },
+          ":data-issue-update" => "'#{project_issues_path(@project)}/' + issue.id + '.json'" }
           %span.dropdown-toggle-text
             Label
           = icon('chevron-down')
diff --git a/app/views/projects/boards/components/sidebar/_milestone.html.haml b/app/views/projects/boards/components/sidebar/_milestone.html.haml
index 4e46351bf8abc64db19d98e8f5916c1e89cd9ce6..002e9994ee0edba65c74cc51d246d67c6b3bfa9b 100644
--- a/app/views/projects/boards/components/sidebar/_milestone.html.haml
+++ b/app/views/projects/boards/components/sidebar/_milestone.html.haml
@@ -16,10 +16,10 @@
         name: "issue[milestone_id]",
         "v-if" => "issue.milestone" }
       .dropdown
-        %button.dropdown-menu-toggle.js-milestone-select.js-issue-board-sidebar{ type: "button", data: { toggle: "dropdown", show_no: "true", field_name: "issue[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), ability_name: "issue", use_id: "true", default_no: "true" },
+        %button.dropdown-menu-toggle.js-milestone-select.js-issue-board-sidebar{ type: "button", data: { toggle: "dropdown", show_no: "true", field_name: "issue[milestone_id]", project_id: @project.id, milestones: project_milestones_path(@project, :json), ability_name: "issue", use_id: "true", default_no: "true" },
           ":data-selected" => "milestoneTitle",
           ":data-issuable-id" => "issue.id",
-          ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" }
+          ":data-issue-update" => "'#{project_issues_path(@project)}/' + issue.id + '.json'" }
           Milestone
           = icon("chevron-down")
         .dropdown-menu.dropdown-select.dropdown-menu-selectable
diff --git a/app/views/projects/boards/components/sidebar/_notifications.html.haml b/app/views/projects/boards/components/sidebar/_notifications.html.haml
index a08c7f2af096a3f94f96597174e23d2ca1b895bf..aaddd7e249fbd2969e5ce61c5c42e8e48d40d7e3 100644
--- a/app/views/projects/boards/components/sidebar/_notifications.html.haml
+++ b/app/views/projects/boards/components/sidebar/_notifications.html.haml
@@ -1,5 +1,5 @@
 - if current_user
-  .block.light.subscription{ ":data-url" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '/toggle_subscription'" }
+  .block.light.subscription{ ":data-url" => "'#{project_issues_path(@project)}/' + issue.id + '/toggle_subscription'" }
     %span.issuable-header-text.hide-collapsed.pull-left
       Notifications
     %button.btn.btn-default.pull-right.js-subscribe-button.issuable-subscribe-button.hide-collapsed{ type: "button" }
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 869633e016d0cdefdeefbd6c8242370f7af9edda..19712a8f1becf707b00f778f62a58fa934a8f3ea 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -6,7 +6,7 @@
 - merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
 %li{ class: "js-branch-#{branch.name}" }
   %div
-    = link_to namespace_project_tree_path(@project.namespace, @project, branch.name), class: 'item-title str-truncated ref-name' do
+    = link_to project_tree_path(@project, branch.name), class: 'item-title str-truncated ref-name' do
       = icon('code-fork')
       = branch.name
     &nbsp;
@@ -25,7 +25,7 @@
           Merge request
 
       - if branch.name != @repository.root_ref
-        = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: branch.name), class: "btn btn-default #{'prepend-left-10' unless merge_project}", method: :post, title: "Compare" do
+        = link_to project_compare_index_path(@project, from: @repository.root_ref, to: branch.name), class: "btn btn-default #{'prepend-left-10' unless merge_project}", method: :post, title: "Compare" do
           Compare
 
       = render 'projects/buttons/download', project: @project, ref: branch.name, pipeline: @refs_pipelines[branch.name]
@@ -42,7 +42,7 @@
               title: "Delete protected branch",
               data: { toggle: "modal",
                 target: "#modal-delete-branch",
-                delete_path: namespace_project_branch_path(@project.namespace, @project, branch.name),
+                delete_path: project_branch_path(@project, branch.name),
                 branch_name: branch.name } }
               = icon("trash-o")
           - else
@@ -51,7 +51,7 @@
               title: "Only a project master or owner can delete a protected branch" }
               = icon("trash-o")
         - else
-          = link_to namespace_project_branch_path(@project.namespace, @project, branch.name),
+          = link_to project_branch_path(@project, branch.name),
             class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip",
             title: "Delete branch",
             method: :delete,
diff --git a/app/views/projects/branches/_commit.html.haml b/app/views/projects/branches/_commit.html.haml
index ad8f9da0621aafe87eb78e1bf897910ee1a3e826..18fbb81c16708907e329d8bfb5c3a37a66e74eca 100644
--- a/app/views/projects/branches/_commit.html.haml
+++ b/app/views/projects/branches/_commit.html.haml
@@ -1,9 +1,9 @@
 .branch-commit
   .icon-container.commit-icon
     = custom_icon("icon_commit")
-  = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-sha"
+  = link_to commit.short_id, project_commit_path(project, commit.id), class: "commit-sha"
   &middot;
   %span.str-truncated
-    = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
+    = link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message"
   &middot;
   #{time_ago_with_tooltip(commit.committed_date)}
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index 4bade77a077670c719b6016ca4cd188389533d10..8bc1996452b681855d10b278d8acc69508c24470 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -6,7 +6,7 @@
   .top-area.adjust
     .nav-text
       Protected branches can be managed in
-      = link_to 'project settings', namespace_project_protected_branches_path(@project.namespace, @project)
+      = link_to 'project settings', project_protected_branches_path(@project)
 
     .nav-controls
       = form_tag(filter_branches_path, method: :get) do
@@ -25,9 +25,9 @@
               = link_to title, filter_branches_path(sort: value), class: ("is-active" if @sort == value)
 
       - if can? current_user, :push_code, @project
-        = link_to namespace_project_merged_branches_path(@project.namespace, @project), class: 'btn btn-inverted btn-remove has-tooltip', title: "Delete all branches that are merged into '#{@project.repository.root_ref}'", method: :delete, data: { confirm: "Deleting the merged branches cannot be undone. Are you sure?", container: 'body' } do
+        = link_to project_merged_branches_path(@project), class: 'btn btn-inverted btn-remove has-tooltip', title: "Delete all branches that are merged into '#{@project.repository.root_ref}'", method: :delete, data: { confirm: "Deleting the merged branches cannot be undone. Are you sure?", container: 'body' } do
           Delete merged branches
-        = link_to new_namespace_project_branch_path(@project.namespace, @project), class: 'btn btn-create' do
+        = link_to new_project_branch_path(@project), class: 'btn btn-create' do
           New branch
 
   - if @branches.any?
diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml
index 5a0eba3551fb1658641ba10a99c9fddb0eb2bec2..03eefcc2b4de82d06b7475da7a31df71922e106c 100644
--- a/app/views/projects/branches/new.html.haml
+++ b/app/views/projects/branches/new.html.haml
@@ -27,7 +27,7 @@
       .help-block Existing branch name, tag, or commit SHA
   .form-actions
     = button_tag 'Create branch', class: 'btn btn-create', tabindex: 3
-    = link_to 'Cancel', namespace_project_branches_path(@project.namespace, @project), class: 'btn btn-cancel'
+    = link_to 'Cancel', project_branches_path(@project), class: 'btn btn-cancel'
 
 :javascript
   var availableRefs = #{@project.repository.ref_names.to_json};
diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml
index 3cf91bf07f79975438a8f2ecf59b597986245b72..883922dbf04738645b5f33d53673d742359549ad 100644
--- a/app/views/projects/buttons/_download.html.haml
+++ b/app/views/projects/buttons/_download.html.haml
@@ -2,7 +2,7 @@
 
 - if !project.empty_repo? && can?(current_user, :download_code, project)
   .project-action-button.dropdown.inline>
-    %button.btn.has-tooltip{ title: 'Download', 'data-toggle' => 'dropdown', 'aria-label' => s_('DownloadSource|Download') }
+    %button.btn.has-tooltip{ title: s_('DownloadSource|Download'), 'data-toggle' => 'dropdown', 'aria-label' => s_('DownloadSource|Download') }
       = icon('download')
       = icon("caret-down")
       %span.sr-only=  _('Select Archive Format')
@@ -10,19 +10,19 @@
       %li.dropdown-header
         #{ _('Source code') }
       %li
-        = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), rel: 'nofollow', download: '' do
+        = link_to archive_project_repository_path(project, ref: ref, format: 'zip'), rel: 'nofollow', download: '' do
           %i.fa.fa-download
           %span=  _('Download zip')
       %li
-        = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow', download: '' do
+        = link_to archive_project_repository_path(project, ref: ref, format: 'tar.gz'), rel: 'nofollow', download: '' do
           %i.fa.fa-download
           %span= _('Download tar.gz')
       %li
-        = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.bz2'), rel: 'nofollow', download: '' do
+        = link_to archive_project_repository_path(project, ref: ref, format: 'tar.bz2'), rel: 'nofollow', download: '' do
           %i.fa.fa-download
           %span= _('Download tar.bz2')
       %li
-        = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar'), rel: 'nofollow', download: '' do
+        = link_to archive_project_repository_path(project, ref: ref, format: 'tar'), rel: 'nofollow', download: '' do
           %i.fa.fa-download
           %span= _('Download tar')
 
@@ -37,7 +37,7 @@
             %li.dropdown-header Previous Artifacts
           - artifacts.each do |job|
             %li
-              = link_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, "#{ref}/download", job: job.name), rel: 'nofollow', download: '' do
+              = link_to latest_succeeded_project_artifacts_path(project, "#{ref}/download", job: job.name), rel: 'nofollow', download: '' do
                 %i.fa.fa-download
                 %span
                   #{ s_('DownloadArtifacts|Download') } '#{job.name}'
diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml
index 312c349da3abd88af67147e3b149dd946d65d59c..b04d6a1fa5e01785f1a35e29886bd79731a8ac43 100644
--- a/app/views/projects/buttons/_dropdown.html.haml
+++ b/app/views/projects/buttons/_dropdown.html.haml
@@ -1,6 +1,6 @@
 - if current_user
   .project-action-button.dropdown.inline
-    %a.btn.dropdown-toggle.has-tooltip{ href: '#', title: 'Create new...', 'data-toggle' => 'dropdown', 'data-container' => 'body', 'aria-label' => 'Create new...' }
+    %a.btn.dropdown-toggle.has-tooltip{ href: '#', title: _('Create new...'), 'data-toggle' => 'dropdown', 'data-container' => 'body', 'aria-label' => _('Create new...') }
       = icon('plus')
       = icon("caret-down")
     %ul.dropdown-menu.dropdown-menu-align-right.project-home-dropdown
@@ -10,19 +10,19 @@
 
       - if can_create_issue
         %li
-          = link_to new_namespace_project_issue_path(@project.namespace, @project) do
+          = link_to new_project_issue_path(@project) do
             = icon('exclamation-circle fw')
             #{ _('New issue') }
 
       - if merge_project
         %li
-          = link_to new_namespace_project_merge_request_path(merge_project.namespace, merge_project) do
+          = link_to project_new_merge_request_path(merge_project) do
             = icon('tasks fw')
             #{ _('New merge request') }
 
       - if can_create_snippet
         %li
-          = link_to new_namespace_project_snippet_path(@project.namespace, @project) do
+          = link_to new_project_snippet_path(@project) do
             = icon('file-text-o fw')
             #{ _('New snippet') }
 
@@ -31,28 +31,28 @@
 
       - if can?(current_user, :push_code, @project)
         %li
-          = link_to namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master') do
+          = link_to project_new_blob_path(@project, @project.default_branch || 'master') do
             = icon('file fw')
             #{ _('New file') }
         %li
-          = link_to new_namespace_project_branch_path(@project.namespace, @project) do
+          = link_to new_project_branch_path(@project) do
             = icon('code-fork fw')
             #{ _('New branch') }
         %li
-          = link_to new_namespace_project_tag_path(@project.namespace, @project) do
+          = link_to new_project_tag_path(@project) do
             = icon('tags fw')
             #{ _('New tag') }
       - elsif current_user && current_user.already_forked?(@project)
         %li
-          = link_to namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master') do
+          = link_to project_new_blob_path(@project, @project.default_branch || 'master') do
             = icon('file fw')
             #{ _('New file') }
       - elsif can?(current_user, :fork_project, @project)
         %li
-          - continue_params = { to:         namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master'),
+          - continue_params = { to:         project_new_blob_path(@project, @project.default_branch || 'master'),
                                 notice:     edit_in_new_fork_notice,
                                 notice_now: edit_in_new_fork_notice_now }
-          - fork_path = namespace_project_forks_path(@project.namespace, @project, namespace_key:  current_user.namespace.id,
+          - fork_path = project_forks_path(@project, namespace_key:  current_user.namespace.id,
                                                                                   continue:       continue_params)
           = link_to fork_path, method: :post do
             = icon('file fw')
diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml
index 7a08bb3749420ed48b197e09322727cfd073d1b5..f45cc7f0f45e3bc4ed3328e96d80eba23c56f358 100644
--- a/app/views/projects/buttons/_fork.html.haml
+++ b/app/views/projects/buttons/_fork.html.haml
@@ -4,11 +4,15 @@
       = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: _('Go to your fork'), class: 'btn has-tooltip' do
         = custom_icon('icon_fork')
         %span= s_('GoToYourFork|Fork')
+    - elsif !current_user.can_create_project?
+      = link_to new_project_fork_path(@project), title: _('You have reached your project limit'), class: 'btn has-tooltip disabled' do
+        = custom_icon('icon_fork')
+        %span= s_('CreateNewFork|Fork')
     - else
-      = link_to new_namespace_project_fork_path(@project.namespace, @project), class: 'btn' do
+      = link_to new_project_fork_path(@project), class: 'btn' do
         = custom_icon('icon_fork')
         %span= s_('CreateNewFork|Fork')
     .count-with-arrow
       %span.arrow
-      = link_to namespace_project_forks_path(@project.namespace, @project), title: n_('Forks', @project.forks_count), class: 'count' do
+      = link_to project_forks_path(@project), title: n_('Fork', 'Forks', @project.forks_count), class: 'count' do
         = @project.forks_count
diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml
index 58413e2fc52ff13d7d407533d80009c216c46513..e248676be0d2a3533be47a525509e5af52ee83c9 100644
--- a/app/views/projects/buttons/_star.html.haml
+++ b/app/views/projects/buttons/_star.html.haml
@@ -1,5 +1,5 @@
 - if current_user
-  = link_to toggle_star_namespace_project_path(@project.namespace, @project), { class: 'btn star-btn toggle-star', method: :post, remote: true } do
+  = link_to toggle_star_project_path(@project), { class: 'btn star-btn toggle-star', method: :post, remote: true } do
     - if current_user.starred?(@project)
       = icon('star')
       %span.starred=  _('Unstar')
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index d9f28d66b664ed9972cf6eaa60e1c831218e267b..c1842527480cc3b0f593f8c6819ba4da49b32b2c 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -14,7 +14,7 @@
 
   %td.branch-commit
     - if can?(current_user, :read_build, job)
-      = link_to namespace_project_job_url(job.project.namespace, job.project, job) do
+      = link_to project_job_url(job.project, job) do
         %span.build-link ##{job.id}
     - else
       %span.build-link ##{job.id}
@@ -30,7 +30,7 @@
         = custom_icon("icon_commit")
 
     - if commit_sha
-      = link_to job.short_sha, namespace_project_commit_path(job.project.namespace, job.project, job.sha), class: "commit-sha"
+      = link_to job.short_sha, project_commit_path(job.project, job.sha), class: "commit-sha"
 
     - if job.stuck?
       = icon('warning', class: 'text-warning has-tooltip', title: 'Job is stuck. Check runners.')
@@ -63,7 +63,7 @@
   - if admin
     %td
       - if job.project
-        = link_to job.project.name_with_namespace, admin_namespace_project_path(job.project.namespace, job.project)
+        = link_to job.project.name_with_namespace, admin_project_path(job.project)
     %td
       - if job.try(:runner)
         = runner_link(job.runner)
@@ -95,16 +95,16 @@
   %td
     .pull-right
       - if can?(current_user, :read_build, job) && job.artifacts?
-        = link_to download_namespace_project_job_artifacts_path(job.project.namespace, job.project, job), rel: 'nofollow', download: '', title: 'Download artifacts', class: 'btn btn-build' do
+        = link_to download_project_job_artifacts_path(job.project, job), rel: 'nofollow', download: '', title: 'Download artifacts', class: 'btn btn-build' do
           = icon('download')
       - if can?(current_user, :update_build, job)
         - if job.active?
-          = link_to cancel_namespace_project_job_path(job.project.namespace, job.project, job, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do
+          = link_to cancel_project_job_path(job.project, job, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do
             = icon('remove', class: 'cred')
         - elsif allow_retry
           - if job.playable? && !admin && can?(current_user, :update_build, job)
-            = link_to play_namespace_project_job_path(job.project.namespace, job.project, job, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do
+            = link_to play_project_job_path(job.project, job, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do
               = custom_icon('icon_play')
           - elsif job.retryable?
-            = link_to retry_namespace_project_job_path(job.project.namespace, job.project, job, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
+            = link_to retry_project_job_path(job.project, job, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
               = icon('repeat')
diff --git a/app/views/projects/commit/_change.html.haml b/app/views/projects/commit/_change.html.haml
index 281d823da52abc1d302398e81908f30ccc1a42fd..d0a380516f92f50efb16dfd9a9b2dd70e9947835 100644
--- a/app/views/projects/commit/_change.html.haml
+++ b/app/views/projects/commit/_change.html.haml
@@ -1,35 +1,36 @@
 - case type.to_s
 - when 'revert'
-  - label = 'Revert'
-  - branch_label = 'Revert in branch'
+  - label = s_('ChangeTypeAction|Revert')
+  - branch_label = s_('ChangeTypeActionLabel|Revert in branch')
+  - revert_merge_request = _('Revert this merge request')
+  - revert_commit = _('Revert this commit')
+  - title = commit.merged_merge_request(current_user) ? revert_merge_request : revert_commit
 - when 'cherry-pick'
-  - label = 'Cherry-pick'
-  - branch_label = 'Pick into branch'
+  - label = s_('ChangeTypeAction|Cherry-pick')
+  - branch_label = s_('ChangeTypeActionLabel|Pick into branch')
+  - title = commit.merged_merge_request(current_user) ? _('Cherry-pick this merge request') : _('Cherry-pick this commit')
 
 .modal{ id: "modal-#{type}-commit" }
   .modal-dialog
     .modal-content
       .modal-header
         %a.close{ href: "#", "data-dismiss" => "modal" } ×
-        %h3.page-title== #{label} this #{commit.change_type_title(current_user)}
+        %h3.page-title= title
       .modal-body
         = form_tag [type.underscore, @project.namespace.becomes(Namespace), @project, commit], method: :post, remote: false, class: "form-horizontal js-#{type}-form js-requires-input" do
           .form-group.branch
             = label_tag 'start_branch', branch_label, class: 'control-label'
             .col-sm-10
               = hidden_field_tag :start_branch, @project.default_branch, id: 'start_branch'
-              = dropdown_tag(@project.default_branch, options: { title: "Switch branch", filter: true, placeholder: "Search branches", toggle_class: 'js-project-refs-dropdown dynamic', dropdown_class: 'dropdown-menu-selectable', data: { field_name: "start_branch", selected: @project.default_branch, start_branch: @project.default_branch, refs_url: namespace_project_branches_path(@project.namespace, @project), submit_form_on_click: false } })
+              = dropdown_tag(@project.default_branch, options: { title: s_("BranchSwitcherTitle|Switch branch"), filter: true, placeholder: s_("BranchSwitcherPlaceholder|Search branches"), toggle_class: 'js-project-refs-dropdown dynamic', dropdown_class: 'dropdown-menu-selectable', data: { field_name: "start_branch", selected: @project.default_branch, start_branch: @project.default_branch, refs_url: project_branches_path(@project), submit_form_on_click: false } })
 
               - if can?(current_user, :push_code, @project)
-                .checkbox
-                  = label_tag do
-                    = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: nil
-                    Start a <strong>new merge request</strong> with these changes
+                = render 'shared/new_merge_request_checkbox'
               - else
                 = hidden_field_tag 'create_merge_request', 1, id: nil
           .form-actions
             = submit_tag label, class: 'btn btn-create'
-            = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
+            = link_to _("Cancel"), '#', class: "btn btn-cancel", "data-dismiss" => "modal"
 
             - unless can?(current_user, :push_code, @project)
               .inline.prepend-left-10
diff --git a/app/views/projects/commit/_ci_menu.html.haml b/app/views/projects/commit/_ci_menu.html.haml
index 8aed88da38babd417f88496e1e124f097ade9aa2..f3f11b5b4052a01340c51d5a15ef8bef0ed549e6 100644
--- a/app/views/projects/commit/_ci_menu.html.haml
+++ b/app/views/projects/commit/_ci_menu.html.haml
@@ -1,10 +1,10 @@
 %ul.nav-links.no-top.no-bottom.commit-ci-menu
   = nav_link(path: 'commit#show') do
-    = link_to namespace_project_commit_path(@project.namespace, @project, @commit.id) do
+    = link_to project_commit_path(@project, @commit.id) do
       Changes
       %span.badge= @diffs.size
   - if can?(current_user, :read_pipeline, @project)
     = nav_link(path: 'commit#pipelines') do
-      = link_to pipelines_namespace_project_commit_path(@project.namespace, @project, @commit.id) do
+      = link_to pipelines_project_commit_path(@project, @commit.id) do
         Pipelines
         %span.badge= @commit.pipelines.size
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index aab503102341cb48105d13418d16e50ec15f8418..572c368990e3d512d29efc41bf8a27582157d323 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -1,17 +1,17 @@
 .page-content-header
   .header-main-content
     %strong
-      Commit
+      #{ s_('CommitBoxTitle|Commit') }
       %span.commit-sha= @commit.short_id
-    = clipboard_button(text: @commit.id, title: "Copy commit SHA to clipboard")
+    = clipboard_button(text: @commit.id, title: _("Copy commit SHA to clipboard"))
     %span.hidden-xs authored
     #{time_ago_with_tooltip(@commit.authored_date)}
-    %span by
+    %span= s_('ByAuthor|by')
     = author_avatar(@commit, size: 24)
     %strong
       = commit_author_link(@commit, avatar: true, size: 24)
     - if @commit.different_committer?
-      %span.light Committed by
+      %span.light= _('Committed by')
       %strong
         = commit_committer_link(@commit, avatar: true, size: 24)
       #{time_ago_with_tooltip(@commit.committed_date)}
@@ -21,30 +21,30 @@
       %span.btn.disabled.btn-grouped.hidden-xs.append-right-10
         = icon('comment')
         = @notes_count
-    = link_to namespace_project_tree_path(@project.namespace, @project, @commit), class: "btn btn-default append-right-10 hidden-xs hidden-sm" do
-      Browse files
+    = link_to project_tree_path(@project, @commit), class: "btn btn-default append-right-10 hidden-xs hidden-sm" do
+      #{ _('Browse files') }
     .dropdown.inline
       %a.btn.btn-default.dropdown-toggle{ data: { toggle: "dropdown" } }
-        %span Options
+        %span= _('Options')
         = icon('caret-down')
       %ul.dropdown-menu.dropdown-menu-align-right
         %li.visible-xs-block.visible-sm-block
-          = link_to namespace_project_tree_path(@project.namespace, @project, @commit) do
-            Browse Files
+          = link_to project_tree_path(@project, @commit) do
+            _('Browse Files')
         - unless @commit.has_been_reverted?(current_user)
           %li.clearfix
-            = revert_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id), has_tooltip: false)
+            = revert_commit_link(@commit, project_commit_path(@project, @commit.id), has_tooltip: false)
         %li.clearfix
-          = cherry_pick_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id), has_tooltip: false)
+          = cherry_pick_commit_link(@commit, project_commit_path(@project, @commit.id), has_tooltip: false)
         - if can_collaborate_with_project?
           %li.clearfix
-            = link_to "Tag", new_namespace_project_tag_path(@project.namespace, @project, ref: @commit)
+            = link_to s_("CreateTag|Tag"), new_project_tag_path(@project, ref: @commit)
         %li.divider
         %li.dropdown-header
-          Download
+          #{ _('Download') }
         - unless @commit.parents.length > 1
-          %li= link_to "Email Patches", namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch)
-        %li= link_to "Plain Diff",    namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff)
+          %li= link_to s_("DownloadCommit|Email Patches"), project_commit_path(@project, @commit, format: :patch)
+        %li= link_to s_("DownloadCommit|Plain Diff"),    project_commit_path(@project, @commit, format: :diff)
 
 .commit-box
   %h3.commit-title
@@ -57,9 +57,9 @@
   .well-segment.branch-info
     .icon-container.commit-icon
       = custom_icon("icon_commit")
-    %span.cgray= pluralize(@commit.parents.count, "parent")
+    %span.cgray= n_('parent', 'parents', @commit.parents.count)
     - @commit.parents.each do |parent|
-      = link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent), class: "commit-sha"
+      = link_to parent.short_id, project_commit_path(@project, parent), class: "commit-sha"
     %span.commit-info.branches
       %i.fa.fa-spinner.fa-spin
 
@@ -67,17 +67,17 @@
     - last_pipeline = @commit.last_pipeline
     .well-segment.pipeline-info
       .status-icon-container{ class: "ci-status-icon-#{@commit.status}" }
-        = link_to namespace_project_pipeline_path(@project.namespace, @project, last_pipeline.id) do
+        = link_to project_pipeline_path(@project, last_pipeline.id) do
           = ci_icon_for_status(last_pipeline.status)
-      Pipeline
-      = link_to "##{last_pipeline.id}", namespace_project_pipeline_path(@project.namespace, @project, last_pipeline.id)
+      #{ _('Pipeline') }
+      = link_to "##{last_pipeline.id}", project_pipeline_path(@project, last_pipeline.id)
       = ci_label_for_status(last_pipeline.status)
       - if last_pipeline.stages_count.nonzero?
-        with #{"stage".pluralize(last_pipeline.stages_count)}
+        #{ n_(s_('Pipeline|with stage'), s_('Pipeline|with stages'), last_pipeline.stages_count) }
         .mr-widget-pipeline-graph
           = render 'shared/mini_pipeline_graph', pipeline: last_pipeline, klass: 'js-commit-pipeline-graph'
       in
       = time_interval_in_words last_pipeline.duration
 
 :javascript
-  $(".commit-info.branches").load("#{branches_namespace_project_commit_path(@project.namespace, @project, @commit.id)}");
+  $(".commit-info.branches").load("#{branches_project_commit_path(@project, @commit.id)}");
diff --git a/app/views/projects/commit/pipelines.html.haml b/app/views/projects/commit/pipelines.html.haml
index ac93eac41acc4c3f882fbb8a0f165219b34a6d30..c66ea873dbadc4a16a24c0dc9565730bdb9df5d4 100644
--- a/app/views/projects/commit/pipelines.html.haml
+++ b/app/views/projects/commit/pipelines.html.haml
@@ -2,4 +2,4 @@
 
 = render 'commit_box'
 = render 'ci_menu'
-= render 'projects/commit/pipelines_list', endpoint: pipelines_namespace_project_commit_path(@project.namespace, @project, @commit.id)
+= render 'projects/commit/pipelines_list', endpoint: pipelines_project_commit_path(@project, @commit.id)
diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml
index 3a1be3fa4b6f28cdb91093d9a5369abc06fe7f5e..b778e8af121f247e76aecf37db2c1fc4571de7dd 100644
--- a/app/views/projects/commit/show.html.haml
+++ b/app/views/projects/commit/show.html.haml
@@ -1,6 +1,6 @@
 - @no_container = true
 - container_class = !fluid_layout && diff_view == :inline ? 'container-limited' : ''
-- limited_container_width = fluid_layout || diff_view == :inline ? '' : 'limit-container-width'
+- limited_container_width = fluid_layout ? '' : 'limit-container-width'
 - page_title        "#{@commit.title} (#{@commit.short_id})", "Commits"
 - page_description  @commit.description
 = render "projects/commits/head"
@@ -13,7 +13,8 @@
     .block-connector
   = render "projects/diffs/diffs", diffs: @diffs, environment: @environment
 
-  = render "shared/notes/notes_with_form", :autocomplete => true
-  - if can_collaborate_with_project?
-    - %w(revert cherry-pick).each do |type|
-      = render "projects/commit/change", type: type, commit: @commit, title: @commit.title
+  .limited-width-notes
+    = render "shared/notes/notes_with_form", :autocomplete => true
+    - if can_collaborate_with_project?
+      - %w(revert cherry-pick).each do |type|
+        = render "projects/commit/change", type: type, commit: @commit, title: @commit.title
diff --git a/app/views/projects/commits/_commit.atom.builder b/app/views/projects/commits/_commit.atom.builder
index 1657fb46163758fe8f9869e17e5079052f134e56..d806acdda1373e9defb9b9f2b91bcb69da9e77ca 100644
--- a/app/views/projects/commits/_commit.atom.builder
+++ b/app/views/projects/commits/_commit.atom.builder
@@ -1,6 +1,6 @@
 xml.entry do
-  xml.id      namespace_project_commit_url(@project.namespace, @project, id: commit.id)
-  xml.link    href: namespace_project_commit_url(@project.namespace, @project, id: commit.id)
+  xml.id      project_commit_url(@project, id: commit.id)
+  xml.link    href: project_commit_url(@project, id: commit.id)
   xml.title   truncate(commit.title, length: 80)
   xml.updated commit.committed_date.xmlschema
   xml.media   :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(commit.author_email))
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 7a03c3561af070040f947d6ee5365f58abcf581b..1033bad0d49e16ebe4e1c490f015808a6f0c3cc5 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -5,7 +5,7 @@
   - notes = commit.notes
   - note_count = notes.user.count
 
-- cache_key = [project.path_with_namespace, commit.id, current_application_settings, note_count]
+- cache_key = [project.path_with_namespace, commit.id, current_application_settings, note_count, @path.presence, current_controller?(:commits)]
 - cache_key.push(commit.status(ref)) if commit.status(ref)
 
 = cache(cache_key, expires_in: 1.day) do
@@ -16,7 +16,7 @@
 
     .commit-detail
       .commit-content
-        = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message item-title"
+        = link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message item-title"
         %span.commit-row-message.visible-xs-inline
           &middot;
           = commit.short_id
@@ -30,13 +30,15 @@
           %pre.commit-row-description.js-toggle-content
             = preserve(markdown(commit.description, pipeline: :single_line, author: commit.author))
         .commiter
-          = commit_author_link(commit, avatar: false, size: 24)
-          #{ _('committed') }
-          #{time_ago_with_tooltip(commit.committed_date)}
+          - commit_author_link = commit_author_link(commit, avatar: false, size: 24)
+          - commit_timeago = time_ago_with_tooltip(commit.committed_date)
+          - commit_text =  _('%{commit_author_link} committed %{commit_timeago}') % { commit_author_link: commit_author_link, commit_timeago: commit_timeago }
+          #{ commit_text.html_safe }
+
 
       .commit-actions.flex-row.hidden-xs
         - if commit.status(ref)
           = render_commit_status(commit, ref: ref)
-        = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-sha btn btn-transparent"
+        = link_to commit.short_id, project_commit_path(project, commit), class: "commit-sha btn btn-transparent"
         = clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard"))
         = link_to_browse_code(project, commit)
diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml
index d3380c917e46e24caf11165e2df898ce517e8666..cf8dffc8957038946e8b020e5f4ec08e59cacdf1 100644
--- a/app/views/projects/commits/_commits.html.haml
+++ b/app/views/projects/commits/_commits.html.haml
@@ -3,13 +3,13 @@
 
 - commits.chunk { |c| c.committed_date.in_time_zone.to_date }.each do |day, commits|
   %li.commit-header.js-commit-header{ data: { day: day } }
-    %span.day= day.strftime('%d %b, %Y')
-    %span.commits-count= pluralize(commits.count, 'commit')
+    %span.day= l(day, format: '%d %b, %Y')
+    %span.commits-count= n_("%d commit", "%d commits", commits.count) % commits.count
 
   %li.commits-row{ data: { day: day } }
     %ul.content-list.commit-list
-      = render commits, project: project, ref: ref
+      = render partial: 'projects/commits/commit', collection: commits, locals: { project: project, ref: ref }
 
 - if hidden > 0
   %li.alert.alert-warning
-    #{number_with_delimiter(hidden)} additional commits have been omitted to prevent performance issues.
+    = n_('%d additional commit has been omitted to prevent performance issues.', '%d additional commits have been omitted to prevent performance issues.', hidden) % number_with_delimiter(hidden)
diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml
index ebeaab863bc112fcb2bda6277f34affc9f25235b..e1549baef89bfac27aa43eff79f3b0fe0f1edbd4 100644
--- a/app/views/projects/commits/_head.html.haml
+++ b/app/views/projects/commits/_head.html.haml
@@ -4,33 +4,33 @@
     .nav-links.sub-nav.scrolling-tabs
       %ul{ class: (container_class) }
         = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do
-          = link_to project_files_path(@project) do
+          = link_to project_tree_path(@project) do
             #{ _('Files') }
 
         = nav_link(controller: [:commit, :commits]) do
-          = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
+          = link_to project_commits_path(@project, current_ref) do
             #{ _('Commits') }
 
         = nav_link(html_options: {class: branches_tab_class}) do
-          = link_to namespace_project_branches_path(@project.namespace, @project) do
+          = link_to project_branches_path(@project) do
             #{ _('Branches') }
 
         = nav_link(controller: [:tags, :releases]) do
-          = link_to namespace_project_tags_path(@project.namespace, @project) do
+          = link_to project_tags_path(@project) do
             #{ _('Tags') }
 
         = nav_link(path: 'graphs#show') do
-          = link_to namespace_project_graph_path(@project.namespace, @project, current_ref) do
+          = link_to project_graph_path(@project, current_ref) do
             #{ _('Contributors') }
 
         = nav_link(controller: %w(network)) do
-          = link_to namespace_project_network_path(@project.namespace, @project, current_ref) do
+          = link_to project_network_path(@project, current_ref) do
             #{ s_('ProjectNetworkGraph|Graph') }
 
         = nav_link(controller: :compare) do
-          = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: current_ref) do
+          = link_to project_compare_index_path(@project, from: @repository.root_ref, to: current_ref) do
             #{ _('Compare') }
 
         = nav_link(path: 'graphs#charts') do
-          = link_to charts_namespace_project_graph_path(@project.namespace, @project, current_ref) do
+          = link_to charts_project_graph_path(@project, current_ref) do
             #{ _('Charts') }
diff --git a/app/views/projects/commits/_inline_commit.html.haml b/app/views/projects/commits/_inline_commit.html.haml
index 5fb899354672f65a1568fbbeee1475f26319d21f..48cefbe45f2e1d1e4b57a063121eb56b2e3f7196 100644
--- a/app/views/projects/commits/_inline_commit.html.haml
+++ b/app/views/projects/commits/_inline_commit.html.haml
@@ -1,8 +1,8 @@
 %li.commit.inline-commit
   .commit-row-title
-    = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-sha"
+    = link_to commit.short_id, project_commit_path(project, commit), class: "commit-sha"
     &nbsp;
     %span.str-truncated
-      = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
+      = link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message"
     .pull-right
       #{time_ago_with_tooltip(commit.committed_date)}
diff --git a/app/views/projects/commits/show.atom.builder b/app/views/projects/commits/show.atom.builder
index 9cf792e1721deaaa90f701fe4e02d79bfe294624..a9b776314749bcc9f6cb4db1cf78ea2a05d5e776 100644
--- a/app/views/projects/commits/show.atom.builder
+++ b/app/views/projects/commits/show.atom.builder
@@ -1,7 +1,7 @@
 xml.title   "#{@project.name}:#{@ref} commits"
-xml.link    href: namespace_project_commits_url(@project.namespace, @project, @ref, rss_url_options), rel: "self", type: "application/atom+xml"
-xml.link    href: namespace_project_commits_url(@project.namespace, @project, @ref), rel: "alternate", type: "text/html"
-xml.id      namespace_project_commits_url(@project.namespace, @project, @ref)
+xml.link    href: project_commits_url(@project, @ref, rss_url_options), rel: "self", type: "application/atom+xml"
+xml.link    href: project_commits_url(@project, @ref), rel: "alternate", type: "text/html"
+xml.id      project_commits_url(@project, @ref)
 xml.updated @commits.first.committed_date.xmlschema if @commits.any?
 
 xml << render(@commits) if @commits.any?
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index c1c2fb3d299eaba2b571d717ac0ec18762ad7706..b8547c10c7380e6660309fcc23619774990529df 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -1,34 +1,35 @@
 - @no_container = true
 
-- page_title "Commits", @ref
+- page_title _("Commits"), @ref
 = content_for :meta_tags do
-  = auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits")
+  = auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits")
 
 = content_for :sub_nav do
   = render "head"
 
 %div{ class: container_class }
-  .row-content-block.second-block.content-component-block.flex-container-block
-    .tree-ref-holder
-      = render 'shared/ref_switcher', destination: 'commits'
+  .tree-holder
+    .nav-block
+      .tree-ref-container
+        .tree-ref-holder
+          = render 'shared/ref_switcher', destination: 'commits'
+
+        %ul.breadcrumb.repo-breadcrumb
+          = commits_breadcrumbs
+      .tree-controls.hidden-xs.hidden-sm
+        - if @merge_request.present?
+          .control
+            = link_to _("View open merge request"), project_merge_request_path(@project, @merge_request), class: 'btn'
+        - elsif create_mr_button?(@repository.root_ref, @ref)
+          .control
+            = link_to _("Create merge request"), create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success'
 
-    %ul.breadcrumb.repo-breadcrumb
-      = commits_breadcrumbs
-
-    .block-controls.hidden-xs.hidden-sm
-      - if @merge_request.present?
         .control
-          = link_to "View open merge request", namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn'
-      - elsif create_mr_button?(@repository.root_ref, @ref)
+          = form_tag(project_commits_path(@project, @id), method: :get, class: 'commits-search-form') do
+            = search_field_tag :search, params[:search], { placeholder: _('Filter by commit message'), id: 'commits-search', class: 'form-control search-text-input input-short', spellcheck: false }
         .control
-          = link_to "Create merge request", create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success'
-
-      .control
-        = form_tag(namespace_project_commits_path(@project.namespace, @project, @id), method: :get, class: 'commits-search-form') do
-          = search_field_tag :search, params[:search], { placeholder: 'Filter by commit message', id: 'commits-search', class: 'form-control search-text-input input-short', spellcheck: false }
-      .control
-        = link_to namespace_project_commits_path(@project.namespace, @project, @ref, rss_url_options), title: "Commits feed", class: 'btn' do
-          = icon("rss")
+          = link_to project_commits_path(@project, @ref, rss_url_options), title: _("Commits feed"), class: 'btn' do
+            = icon("rss")
 
   %div{ id: dom_id(@project) }
     %ol#commits-list.list-unstyled.content_list
diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml
index adb724c1b8d1ceaff3a08f47855c4b28f1138b31..94b7db5eb25efdb2e0334c0393f6233872ed4afd 100644
--- a/app/views/projects/compare/_form.html.haml
+++ b/app/views/projects/compare/_form.html.haml
@@ -1,4 +1,4 @@
-= form_tag namespace_project_compare_index_path(@project.namespace, @project), method: :post, class: 'form-inline js-requires-input' do
+= form_tag project_compare_index_path(@project), method: :post, class: 'form-inline js-requires-input' do
   .clearfix
     - if params[:to] && params[:from]
       .compare-switch-container
@@ -7,7 +7,7 @@
       .input-group.inline-input-group
         %span.input-group-addon from
         = hidden_field_tag :from, params[:from]
-        = button_tag type: 'button', title: params[:from], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do
+        = button_tag type: 'button', title: params[:from], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do
           .dropdown-toggle-text.str-truncated= params[:from] || 'Select branch/tag'
       = render 'shared/ref_dropdown'
     .compare-ellipsis.inline ...
@@ -15,12 +15,12 @@
       .input-group.inline-input-group
         %span.input-group-addon to
         = hidden_field_tag :to, params[:to]
-        = button_tag type: 'button', title: params[:to], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to], field_name: :to } do
+        = button_tag type: 'button', title: params[:to], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to], field_name: :to } do
           .dropdown-toggle-text.str-truncated= params[:to] || 'Select branch/tag'
       = render 'shared/ref_dropdown'
     &nbsp;
     = button_tag "Compare", class: "btn btn-create commits-compare-btn"
     - if @merge_request.present?
-      = link_to "View open merge request", namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'prepend-left-10 btn'
+      = link_to "View open merge request", project_merge_request_path(@project, @merge_request), class: 'prepend-left-10 btn'
     - elsif create_mr_button?
       = link_to "Create merge request", create_mr_path, class: 'prepend-left-10 btn'
diff --git a/app/views/projects/deploy_keys/_index.html.haml b/app/views/projects/deploy_keys/_index.html.haml
index 6e038ffd9c0f1d3cd7d8eeec5b8e59ddbe642a78..45985a5ecefa04904d548fed9170edb0d3446df0 100644
--- a/app/views/projects/deploy_keys/_index.html.haml
+++ b/app/views/projects/deploy_keys/_index.html.haml
@@ -4,7 +4,7 @@
     %h4
       Deploy Keys
     %button.btn.js-settings-toggle
-      = expanded ? 'Close' : 'Expand'
+      = expanded ? 'Collapse' : 'Expand'
     %p
       Deploy keys allow read-only or read-write (if enabled) access to your repository. Deploy keys can be used for CI, staging or production servers. You can create a deploy key or add an existing one.
   .settings-content.no-animate{ class: ('expanded' if expanded) }
@@ -12,4 +12,4 @@
       Create a new deploy key for this project
     = render @deploy_keys.form_partial_path
     %hr
-    #js-deploy-keys{ data: { endpoint: namespace_project_deploy_keys_path } }
+    #js-deploy-keys{ data: { endpoint: project_deploy_keys_path(@project) } }
diff --git a/app/views/projects/deploy_keys/edit.html.haml b/app/views/projects/deploy_keys/edit.html.haml
index 37219f8d7aece998be6229d7b0f1975803b57546..cd910b82b577b511f60ec3a3cb826c368cf7ccba 100644
--- a/app/views/projects/deploy_keys/edit.html.haml
+++ b/app/views/projects/deploy_keys/edit.html.haml
@@ -7,4 +7,4 @@
     = render partial: 'shared/deploy_keys/form', locals: { form: f, deploy_key: @deploy_key }
     .form-actions
       = f.submit 'Save changes', class: 'btn-save btn'
-      = link_to 'Cancel', namespace_project_settings_repository_path(@project.namespace, @project), class: 'btn btn-cancel'
+      = link_to 'Cancel', project_settings_repository_path(@project), class: 'btn btn-cancel'
diff --git a/app/views/projects/deployments/_commit.html.haml b/app/views/projects/deployments/_commit.html.haml
index 4502c397d29b2b121733cc9e3c41d194ceaccb77..4c22166c25671ac12553256a63ea7cef01ff4a79 100644
--- a/app/views/projects/deployments/_commit.html.haml
+++ b/app/views/projects/deployments/_commit.html.haml
@@ -6,12 +6,12 @@
       = link_to deployment.ref, project_ref_path(@project, deployment.ref), class: "ref-name"
     .icon-container.commit-icon
       = custom_icon("icon_commit")
-    = link_to deployment.short_sha, namespace_project_commit_path(@project.namespace, @project, deployment.sha), class: "commit-sha"
+    = link_to deployment.short_sha, project_commit_path(@project, deployment.sha), class: "commit-sha"
 
     %p.commit-title.flex-truncate-parent
       %span.flex-truncate-child
         - if commit_title = deployment.commit_title
           = author_avatar(deployment.commit, size: 20)
-          = link_to_gfm commit_title, namespace_project_commit_path(@project.namespace, @project, deployment.sha), class: "commit-row-message"
+          = link_to_gfm commit_title, project_commit_path(@project, deployment.sha), class: "commit-row-message"
         - else
           Cant find HEAD commit for this branch
diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml
index 9b2ec9ae41c32fbd84442b31449f55580e3c20c6..520696b01c6dcef9c89bb2e0a0fece5a6c3642bb 100644
--- a/app/views/projects/deployments/_deployment.html.haml
+++ b/app/views/projects/deployments/_deployment.html.haml
@@ -3,24 +3,28 @@
     .table-mobile-header{ role: 'rowheader' } ID
     %strong.table-mobile-content ##{deployment.iid}
 
-  .table-section.section-40{ role: 'gridcell' }
+  .table-section.section-30{ role: 'gridcell' }
     .table-mobile-header{ role: 'rowheader' } Commit
     = render 'projects/deployments/commit', deployment: deployment
 
-  .table-section.section-15.build-column{ role: 'gridcell' }
+  .table-section.section-25.build-column{ role: 'gridcell' }
     .table-mobile-header{ role: 'rowheader' } Job
     - if deployment.deployable
-      = link_to [@project.namespace.becomes(Namespace), @project, deployment.deployable], class: 'build-link table-mobile-content' do
-        #{deployment.deployable.name} (##{deployment.deployable.id})
-      - if deployment.user
-        by
-        = user_avatar(user: deployment.user, size: 20)
+      .table-mobile-content
+        .flex-truncate-parent
+          .flex-truncate-child
+            = link_to [@project.namespace.becomes(Namespace), @project, deployment.deployable], class: 'build-link' do
+              #{deployment.deployable.name} (##{deployment.deployable.id})
+        - if deployment.user
+          %div
+            by
+            = user_avatar(user: deployment.user, size: 20)
 
   .table-section.section-15{ role: 'gridcell' }
     .table-mobile-header{ role: 'rowheader' } Created
     %span.table-mobile-content= time_ago_with_tooltip(deployment.created_at)
 
   .table-section.section-20.table-button-footer{ role: 'gridcell' }
-    .btn-group.table-action-button
+    .btn-group.table-action-buttons
       = render 'projects/deployments/actions', deployment: deployment
       = render 'projects/deployments/rollback', deployment: deployment
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index d538c4c86c86164657b19698c0725b77358692bf..f9385459a66111b3daace0dbba8ff4757fcc988c 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -10,7 +10,7 @@
     - if show_whitespace_toggle
       - if current_controller?(:commit)
         = commit_diff_whitespace_link(diffs.project, @commit, class: 'hidden-xs')
-      - elsif current_controller?(:merge_requests)
+      - elsif current_controller?('projects/merge_requests/diffs')
         = diff_merge_request_whitespace_link(diffs.project, @merge_request, class: 'hidden-xs')
       - elsif current_controller?(:compare)
         = diff_compare_whitespace_link(diffs.project, params[:from], params[:to], class: 'hidden-xs')
diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml
index 43708d22a0ce9b984e22540978df323d62f4bf2a..cd0fb21f8a7539f09f7e7ecd370f8be875f636a2 100644
--- a/app/views/projects/diffs/_line.html.haml
+++ b/app/views/projects/diffs/_line.html.haml
@@ -19,6 +19,7 @@
       - if plain
         = link_text
       - else
+        = add_diff_note_button(line_code, diff_file.position(line), type)
         %a{ href: "##{line_code}", data: { linenumber: link_text } }
       - discussion = line_discussions.try(:first)
       - if discussion && discussion.resolvable? && !plain
@@ -29,7 +30,7 @@
         = link_text
       - else
         %a{ href: "##{line_code}", data: { linenumber: link_text } }
-    %td.line_content.noteable_line{ class: type, data: (diff_view_line_data(line_code, diff_file.position(line), type) unless plain) }<
+    %td.line_content.noteable_line{ class: type }<
       - if email
         %pre= line.text
       - else
diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml
index 8e5f4d2573d65bc0444fe522410ee7be9e3ef436..56d63250714f303c14bace3e51fb59b63d363d7e 100644
--- a/app/views/projects/diffs/_parallel_view.html.haml
+++ b/app/views/projects/diffs/_parallel_view.html.haml
@@ -1,4 +1,5 @@
 / Side-by-side diff view
+
 .text-file.diff-wrap-lines.code.js-syntax-highlight{ data: diff_view_data }
   %table
     - diff_file.parallel_diff_lines.each do |line|
@@ -18,11 +19,12 @@
             - left_line_code = diff_file.line_code(left)
             - left_position = diff_file.position(left)
             %td.old_line.diff-line-num.js-avatar-container{ id: left_line_code, class: left.type, data: { linenumber: left.old_pos } }
+              = add_diff_note_button(left_line_code, left_position, 'old')
               %a{ href: "##{left_line_code}", data: { linenumber: left.old_pos } }
               - discussion_left = discussions_left.try(:first)
               - if discussion_left && discussion_left.resolvable?
                 %diff-note-avatars{ "discussion-id" => discussion_left.id }
-            %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)
+            %td.line_content.parallel.noteable_line{ class: left.type }= diff_line_content(left.text)
         - else
           %td.old_line.diff-line-num.empty-cell
           %td.line_content.parallel
@@ -38,11 +40,12 @@
             - right_line_code = diff_file.line_code(right)
             - right_position = diff_file.position(right)
             %td.new_line.diff-line-num.js-avatar-container{ id: right_line_code, class: right.type, data: { linenumber: right.new_pos } }
+              = add_diff_note_button(right_line_code, right_position, 'new')
               %a{ href: "##{right_line_code}", data: { linenumber: right.new_pos } }
               - discussion_right = discussions_right.try(:first)
               - if discussion_right && discussion_right.resolvable?
                 %diff-note-avatars{ "discussion-id" => discussion_right.id }
-            %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)
+            %td.line_content.parallel.noteable_line{ class: right.type }= diff_line_content(right.text)
         - else
           %td.old_line.diff-line-num.empty-cell
           %td.line_content.parallel
diff --git a/app/views/projects/diffs/_warning.html.haml b/app/views/projects/diffs/_warning.html.haml
index 295a1b62535d47b97a7c6e6d66438c1a0de661fe..da34a83d8e0c9dae8bff83841873ddd54df8cd0c 100644
--- a/app/views/projects/diffs/_warning.html.haml
+++ b/app/views/projects/diffs/_warning.html.haml
@@ -2,13 +2,12 @@
   %h4
     Too many changes to show.
     .pull-right
-      - if current_controller?(:commit) or current_controller?(:merge_requests)
-        - if current_controller?(:commit)
-          = link_to "Plain diff", namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff), class: "btn btn-sm"
-          = link_to "Email patch", namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch), class: "btn btn-sm"
-        - elsif @merge_request && @merge_request.persisted?
-          = link_to "Plain diff", merge_request_path(@merge_request, format: :diff), class: "btn btn-sm"
-          = link_to "Email patch", merge_request_path(@merge_request, format: :patch), class: "btn btn-sm"
+      - if current_controller?(:commit)
+        = link_to "Plain diff", project_commit_path(@project, @commit, format: :diff), class: "btn btn-sm"
+        = link_to "Email patch", project_commit_path(@project, @commit, format: :patch), class: "btn btn-sm"
+      - elsif current_controller?('projects/merge_requests/diffs') && @merge_request&.persisted?
+        = link_to "Plain diff", merge_request_path(@merge_request, format: :diff), class: "btn btn-sm"
+        = link_to "Email patch", merge_request_path(@merge_request, format: :patch), class: "btn btn-sm"
   %p
     To preserve performance only
     %strong #{diff_files.size} of #{diff_files.real_size}
diff --git a/app/views/projects/diffs/viewers/_image.html.haml b/app/views/projects/diffs/viewers/_image.html.haml
index 19d08181223ab8255c112e730386e30b2ac411cb..33d3dcbeafae48755283d011a3fbfade86084450 100644
--- a/app/views/projects/diffs/viewers/_image.html.haml
+++ b/app/views/projects/diffs/viewers/_image.html.haml
@@ -15,7 +15,7 @@
     .two-up.view
       %span.wrap
         .frame.deleted
-          %a{ href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.old_content_sha, diff_file.old_path)) }
+          %a{ href: project_blob_path(@project, tree_join(diff_file.old_content_sha, diff_file.old_path)) }
             %img{ src: old_blob_raw_path, alt: diff_file.old_path }
         %p.image-info.hide
           %span.meta-filesize= number_to_human_size(old_blob.size)
@@ -27,7 +27,7 @@
           %span.meta-height
       %span.wrap
         .frame.added
-          %a{ href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.content_sha, diff_file.new_path)) }
+          %a{ href: project_blob_path(@project, tree_join(diff_file.content_sha, diff_file.new_path)) }
             %img{ src: blob_raw_path, alt: diff_file.new_path }
         %p.image-info.hide
           %span.meta-filesize= number_to_human_size(blob.size)
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index c3dab68cea57c09254a0627bd34b6522db0ce712..087cb8044490bd31805142bc784a2b670cd65f55 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -1,10 +1,12 @@
+- @content_class = "limit-container-width" unless fluid_layout
+
 = render "projects/settings/head"
 .project-edit-container
   .row.prepend-top-default
-    .col-lg-3.profile-settings-sidebar
+    .col-lg-4.profile-settings-sidebar
       %h4.prepend-top-0
         Project settings
-    .col-lg-9
+    .col-lg-8
       .project-edit-errors
       = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit-project" }, authenticity_token: true do |f|
         %fieldset
@@ -39,69 +41,69 @@
             Sharing &amp; Permissions
           .form_group.prepend-top-20.sharing-and-permissions
             .row.js-visibility-select
-              .col-md-9
+              .col-md-8
                 .label-light
                   = label_tag :project_visibility, 'Project Visibility', class: 'label-light', for: :project_visibility_level
                   = link_to icon('question-circle'), help_page_path("public_access/public_access")
                 %span.help-block
-              .col-md-3.visibility-select-container
+              .col-md-4.visibility-select-container
                 = render('projects/visibility_select', model_method: :visibility_level, form: f, selected_level: @project.visibility_level)
             = f.fields_for :project_feature do |feature_fields|
               %fieldset.features
                 .row
-                  .col-md-9.project-feature
+                  .col-md-8.project-feature
                     = feature_fields.label :repository_access_level, "Repository", class: 'label-light'
                     %span.help-block View and edit files in this project
-                  .col-md-3.js-repo-access-level
+                  .col-md-4.js-repo-access-level
                     = project_feature_access_select(:repository_access_level)
 
                 .row
-                  .col-md-9.project-feature.nested
+                  .col-md-8.project-feature.nested
                     = feature_fields.label :merge_requests_access_level, "Merge requests", class: 'label-light'
                     %span.help-block Submit changes to be merged upstream
-                  .col-md-3
+                  .col-md-4
                     = project_feature_access_select(:merge_requests_access_level)
 
                 .row
-                  .col-md-9.project-feature.nested
+                  .col-md-8.project-feature.nested
                     = feature_fields.label :builds_access_level, "Pipelines", class: 'label-light'
                     %span.help-block Build, test, and deploy your changes
-                  .col-md-3
+                  .col-md-4
                     = project_feature_access_select(:builds_access_level)
 
                 .row
-                  .col-md-9.project-feature
+                  .col-md-8.project-feature
                     = feature_fields.label :snippets_access_level, "Snippets", class: 'label-light'
                     %span.help-block Share code pastes with others out of Git repository
-                  .col-md-3
+                  .col-md-4
                     = project_feature_access_select(:snippets_access_level)
 
                 .row
-                  .col-md-9.project-feature
+                  .col-md-8.project-feature
                     = feature_fields.label :issues_access_level, "Issues", class: 'label-light'
                     %span.help-block Lightweight issue tracking system for this project
-                  .col-md-3
+                  .col-md-4
                     = project_feature_access_select(:issues_access_level)
 
                 .row
-                  .col-md-9.project-feature
+                  .col-md-8.project-feature
                     = feature_fields.label :wiki_access_level, "Wiki", class: 'label-light'
                     %span.help-block Pages for project documentation
-                  .col-md-3
+                  .col-md-4
                     = project_feature_access_select(:wiki_access_level)
           .form-group
             = render 'shared/allow_request_access', form: f
           - if Gitlab.config.lfs.enabled && current_user.admin?
             .row.js-lfs-enabled
-              .col-md-9
+              .col-md-8
                 = f.label :lfs_enabled, 'LFS', class: 'label-light'
                 %span.help-block
                   Git Large File Storage
                   = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
-              .col-md-3
-                = f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control project-repo-select', data: { field: 'lfs_enabled' }
-
-
+              .col-md-4
+                .select-wrapper
+                  = f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control project-repo-select select-control', data: { field: 'lfs_enabled' }
+                  = icon('chevron-down')
         - if Gitlab.config.registry.enabled
           .form-group.js-container-registry{ style: ("display: none;" if @project.project_feature.send(:repository_access_level) == 0) }
             .checkbox
@@ -132,25 +134,25 @@
             .help-block The maximum file size allowed is 200KB.
             - if @project.avatar?
               %hr
-              = link_to 'Remove avatar', namespace_project_avatar_path(@project.namespace, @project), data: { confirm: "Project avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar"
+              = link_to 'Remove avatar', project_avatar_path(@project), data: { confirm: "Project avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar"
         = f.submit 'Save changes', class: "btn btn-save"
 
   .row.prepend-top-default
   %hr
   .row.prepend-top-default
-    .col-lg-3
+    .col-lg-4
       %h4.prepend-top-0
         Housekeeping
       %p.append-bottom-0
         %p
           Runs a number of housekeeping tasks within the current repository,
           such as compressing file revisions and removing unreachable objects.
-    .col-lg-9
-      = link_to 'Housekeeping', housekeeping_namespace_project_path(@project.namespace, @project),
+    .col-lg-8
+      = link_to 'Housekeeping', housekeeping_project_path(@project),
           method: :post, class: "btn btn-default"
   %hr
   .row.prepend-top-default
-    .col-lg-3
+    .col-lg-4
       %h4.prepend-top-0
         Export project
       %p.append-bottom-0
@@ -159,15 +161,15 @@
         %p
           Once the exported file is ready, you will receive a notification email with a download link.
 
-    .col-lg-9
+    .col-lg-8
 
       - if @project.export_project_path
-        = link_to 'Download export',  download_export_namespace_project_path(@project.namespace, @project),
+        = link_to 'Download export',  download_export_project_path(@project),
                 rel: 'nofollow', download: '', method: :get, class: "btn btn-default"
-        = link_to 'Generate new export',  generate_new_export_namespace_project_path(@project.namespace, @project),
+        = link_to 'Generate new export',  generate_new_export_project_path(@project),
                 method: :post, class: "btn btn-default"
       - else
-        = link_to 'Export project', export_namespace_project_path(@project.namespace, @project),
+        = link_to 'Export project', export_project_path(@project),
                 method: :post, class: "btn btn-default"
 
       .bs-callout.bs-callout-info
@@ -190,7 +192,7 @@
   - if can? current_user, :archive_project, @project
     %hr
     .row.prepend-top-default
-      .col-lg-3
+      .col-lg-4
         %h4.warning-title.prepend-top-0
           - if @project.archived?
             Unarchive project
@@ -201,25 +203,25 @@
             Unarchiving the project will mark its repository as active. The project can be committed to.
           - else
             Archiving the project will mark its repository as read-only. It is hidden from the dashboard and doesn't show up in searches.
-      .col-lg-9
+      .col-lg-8
         - if @project.archived?
           %p
             %strong Once active this project shows up in the search and on the dashboard.
-          = link_to 'Unarchive project', unarchive_namespace_project_path(@project.namespace, @project),
+          = link_to 'Unarchive project', unarchive_project_path(@project),
               data: { confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be committed to again." },
               method: :post, class: "btn btn-success"
         - else
           %p
             %strong Archived projects cannot be committed to!
-          = link_to 'Archive project', archive_namespace_project_path(@project.namespace, @project),
+          = link_to 'Archive project', archive_project_path(@project),
               data: { confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to." },
               method: :post, class: "btn btn-warning"
   %hr
   .row.prepend-top-default
-    .col-lg-3
+    .col-lg-4
       %h4.prepend-top-0.warning-title
         Rename repository
-    .col-lg-9
+    .col-lg-8
       = render 'projects/errors'
       = form_for([@project.namespace.becomes(Namespace), @project]) do |f|
         .form-group.project_name_holder
@@ -244,13 +246,13 @@
   - if can?(current_user, :change_namespace, @project)
     %hr
     .row.prepend-top-default
-      .col-lg-3
+      .col-lg-4
         %h4.prepend-top-0.danger-title
           Transfer project to new group
         %p.append-bottom-0
           Please select the group you want to transfer this project to in the dropdown to the right.
-      .col-lg-9
-        = form_for([@project.namespace.becomes(Namespace), @project], url: transfer_namespace_project_path(@project.namespace, @project), method: :put, remote: true, html: { class: 'js-project-transfer-form' } ) do |f|
+      .col-lg-8
+        = form_for([@project.namespace.becomes(Namespace), @project], url: transfer_project_path(@project), method: :put, remote: true, html: { class: 'js-project-transfer-form' } ) do |f|
           .form-group
             = label_tag :new_namespace_id, nil, class: 'label-light' do
               %span  Select a new namespace
@@ -265,7 +267,7 @@
   - if @project.forked? && can?(current_user, :remove_fork_project, @project)
     %hr
     .row.prepend-top-default.append-bottom-default
-      .col-lg-3
+      .col-lg-4
         %h4.prepend-top-0.danger-title
           Remove fork relationship
         %p.append-bottom-0
@@ -273,21 +275,21 @@
             This will remove the fork relationship to source project
             = succeed "." do
               = link_to @project.forked_from_project.name_with_namespace, project_path(@project.forked_from_project)
-      .col-lg-9
-        = form_for([@project.namespace.becomes(Namespace), @project], url: remove_fork_namespace_project_path(@project.namespace, @project), method: :delete, remote: true, html: { class: 'transfer-project' }) do |f|
+      .col-lg-8
+        = form_for([@project.namespace.becomes(Namespace), @project], url: remove_fork_project_path(@project), method: :delete, remote: true, html: { class: 'transfer-project' }) do |f|
           %p
             %strong Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source.
           = button_to 'Remove fork relationship', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_fork_project_message(@project) }
   - if can?(current_user, :remove_project, @project)
     %hr
     .row.prepend-top-default.append-bottom-default
-      .col-lg-3
+      .col-lg-4
         %h4.prepend-top-0.danger-title
           Remove project
         %p.append-bottom-0
           Removing the project will delete its repository and all related resources including issues, merge requests etc.
-      .col-lg-9
-        = form_tag(namespace_project_path(@project.namespace, @project), method: :delete) do
+      .col-lg-8
+        = form_tag(project_path(@project), method: :delete) do
           %p
             %strong Removed projects cannot be restored!
           = button_to 'Remove project', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_project_message(@project) }
diff --git a/app/views/projects/environments/_form.html.haml b/app/views/projects/environments/_form.html.haml
index 6d040f5cfe6790e4176c31f66528ad5447fd4388..1605f3a33513d35ca1463ea43d23093427ae3ee2 100644
--- a/app/views/projects/environments/_form.html.haml
+++ b/app/views/projects/environments/_form.html.haml
@@ -19,4 +19,4 @@
 
     .form-actions
       = f.submit 'Save', class: 'btn btn-save'
-      = link_to 'Cancel', namespace_project_environments_path(@project.namespace, @project), class: 'btn btn-cancel'
+      = link_to 'Cancel', project_environments_path(@project), class: 'btn btn-cancel'
diff --git a/app/views/projects/environments/_stop.html.haml b/app/views/projects/environments/_stop.html.haml
index 14a2d627203544d1d7c260bca6eeb1eb12d4b195..c35f9af2873784bedf386da7cbf1e3d0f21b07b5 100644
--- a/app/views/projects/environments/_stop.html.haml
+++ b/app/views/projects/environments/_stop.html.haml
@@ -1,5 +1,5 @@
 - if can?(current_user, :create_deployment, environment) && environment.stop_action?
   .inline
-    = link_to stop_namespace_project_environment_path(@project.namespace, @project, environment), method: :post,
+    = link_to stop_project_environment_path(@project, environment), method: :post,
       class: 'btn stop-env-link', rel: 'nofollow', data: { confirm: 'Are you sure you want to stop this environment?' } do
       = icon('stop', class: 'stop-env-icon')
diff --git a/app/views/projects/environments/_terminal_button.html.haml b/app/views/projects/environments/_terminal_button.html.haml
index 97de9c95de7a940e29f299444476c02d03ddb690..a6201bdbc42c05e478dac3c615da125d0419d12a 100644
--- a/app/views/projects/environments/_terminal_button.html.haml
+++ b/app/views/projects/environments/_terminal_button.html.haml
@@ -1,3 +1,3 @@
 - if environment.has_terminals? && can?(current_user, :admin_environment, @project)
-  = link_to terminal_namespace_project_environment_path(@project.namespace, @project, environment), class: 'btn terminal-button' do
+  = link_to terminal_project_environment_path(@project, environment), class: 'btn terminal-button' do
     = icon('terminal')
diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml
index 80d2b6f5d9505b03d95d8f837fb5635e962832ef..30cdbc5ae04f45c1b9d5f55e1ccf1d0270cfadcf 100644
--- a/app/views/projects/environments/index.html.haml
+++ b/app/views/projects/environments/index.html.haml
@@ -12,6 +12,6 @@
   "can-create-environment" => can?(current_user, :create_environment, @project).to_s,
   "project-environments-path" => project_environments_path(@project),
   "project-stopped-environments-path" => project_environments_path(@project, scope: :stopped),
-  "new-environment-path" => new_namespace_project_environment_path(@project.namespace, @project),
+  "new-environment-path" => new_project_environment_path(@project),
   "help-page-path" => help_page_path("ci/environments"),
   "css-class" => container_class } }
diff --git a/app/views/projects/environments/metrics.html.haml b/app/views/projects/environments/metrics.html.haml
index e8f8fbbcf092594ecb4b8a672538bb3abe54aee2..e9e1ad9ef3085a5cd0a1af4a10ac1339412239da 100644
--- a/app/views/projects/environments/metrics.html.haml
+++ b/app/views/projects/environments/metrics.html.haml
@@ -1,80 +1,21 @@
 - @no_container = true
 - page_title "Metrics for environment", @environment.name
 - content_for :page_specific_javascripts do
-  = page_specific_javascript_bundle_tag('common_d3')
-  = page_specific_javascript_bundle_tag('monitoring')
+  = webpack_bundle_tag 'common_vue'
+  = webpack_bundle_tag 'common_d3'
+  = webpack_bundle_tag 'monitoring'
 = render "projects/pipelines/head"
 
-#js-metrics.prometheus-container{ class: container_class, data: { has_metrics: "#{@environment.has_metrics?}", deployment_endpoint: namespace_project_environment_deployments_path(@project.namespace, @project, @environment, format: :json) } }
+.prometheus-container{ class: container_class }
   .top-area
     .row
       .col-sm-6
-        %h3.page-title
+        %h3
           Environment:
           = link_to @environment.name, environment_path(@environment)
 
-  .prometheus-state
-    .js-getting-started.hidden
-      .row
-        .col-md-4.col-md-offset-4.state-svg
-          = render "shared/empty_states/monitoring/getting_started.svg"
-      .row
-        .col-md-6.col-md-offset-3
-          %h4.text-center.state-title
-            Get started with performance monitoring
-      .row
-        .col-md-6.col-md-offset-3
-          .description-text.text-center.state-description
-            Stay updated about the performance and health of your environment by configuring Prometheus to monitor your deployments.
-            = link_to help_page_path('administration/monitoring/prometheus/index.md') do
-              Learn more about performance monitoring
-      .row.state-button-section
-        .col-md-4.col-md-offset-4.text-center.state-button
-          = link_to edit_namespace_project_service_path(@project.namespace, @project, 'prometheus'), class: 'btn btn-success' do
-            Configure Prometheus
-    .js-loading.hidden
-      .row
-        .col-md-4.col-md-offset-4.state-svg
-          = render "shared/empty_states/monitoring/loading.svg"
-      .row
-        .col-md-6.col-md-offset-3
-          %h4.text-center.state-title
-            Waiting for performance data
-      .row
-        .col-md-6.col-md-offset-3
-          .description-text.text-center.state-description
-            Creating graphs uses the data from the Prometheus server. If this takes a long time, ensure that data is available.
-      .row.state-button-section
-        .col-md-4.col-md-offset-4.text-center.state-button
-          = link_to help_page_path('administration/monitoring/prometheus/index.md'), class: 'btn btn-success' do
-            View documentation
-    .js-unable-to-connect.hidden
-      .row
-        .col-md-4.col-md-offset-4.state-svg
-          = render "shared/empty_states/monitoring/unable_to_connect.svg"
-      .row
-        .col-md-6.col-md-offset-3
-          %h4.text-center.state-title
-            Unable to connect to Prometheus server
-      .row
-        .col-md-6.col-md-offset-3
-          .description-text.text-center.state-description
-            Ensure connectivity is available from the GitLab server to the
-            = link_to edit_namespace_project_service_path(@project.namespace, @project, 'prometheus') do
-              Prometheus server
-      .row.state-button-section
-        .col-md-4.col-md-offset-4.text-center.state-button
-          = link_to help_page_path('administration/monitoring/prometheus/index.md'), class:'btn btn-success' do
-            View documentation
+  #prometheus-graphs{ data: { "settings-path": edit_project_service_path(@project, 'prometheus'),
+    "documentation-path": help_page_path('administration/monitoring/prometheus/index.md'),
+    "additional-metrics": additional_metrics_project_environment_path(@project, @environment, format: :json),
+    "has-metrics": "#{@environment.has_metrics?}", deployment_endpoint: project_environment_deployments_path(@project, @environment, format: :json) } }
 
-  .prometheus-graphs
-    .row
-      .col-sm-12
-        %h4
-          CPU utilization
-        %svg.prometheus-graph{ 'graph-type' => 'cpu_values' }
-    .row
-      .col-sm-12
-        %h4
-          Memory usage
-        %svg.prometheus-graph{ 'graph-type' => 'memory_values' }
diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml
index 23aa4c29e6932de74883a2dce21f9b3b7142c861..0ce0f5465fc20cc91e7a7eaa17cec0deff8843d2 100644
--- a/app/views/projects/environments/show.html.haml
+++ b/app/views/projects/environments/show.html.haml
@@ -12,9 +12,9 @@
         = render 'projects/environments/external_url', environment: @environment
         = render 'projects/environments/metrics_button', environment: @environment
         - if can?(current_user, :update_environment, @environment)
-          = link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn'
+          = link_to 'Edit', edit_project_environment_path(@project, @environment), class: 'btn'
         - if can?(current_user, :stop_environment, @environment)
-          = link_to 'Stop', stop_namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to stop this environment?' }, class: 'btn btn-danger', method: :post
+          = link_to 'Stop', stop_project_environment_path(@project, @environment), data: { confirm: 'Are you sure you want to stop this environment?' }, class: 'btn btn-danger', method: :post
 
   .environments-container
     - if @deployments.blank?
@@ -31,8 +31,8 @@
         .ci-table.environments{ role: 'grid' }
           .gl-responsive-table-row.table-row-header{ role: 'row' }
             .table-section.section-10{ role: 'columnheader' } ID
-            .table-section.section-40{ role: 'columnheader' } Commit
-            .table-section.section-15{ role: 'columnheader' } Job
+            .table-section.section-30{ role: 'columnheader' } Commit
+            .table-section.section-25{ role: 'columnheader' } Job
             .table-section.section-15{ role: 'columnheader' } Created
 
           = render @deployments
diff --git a/app/views/projects/environments/terminal.html.haml b/app/views/projects/environments/terminal.html.haml
index 4c4aa0baff3fac1385d2c6f53b44a9e033416498..464135b5ac769568584abcffb9b0ccd8233cae51 100644
--- a/app/views/projects/environments/terminal.html.haml
+++ b/app/views/projects/environments/terminal.html.haml
@@ -22,4 +22,4 @@
           = render 'projects/deployments/actions', deployment: @environment.last_deployment
 
 .terminal-container{ class: container_class }
-  #terminal{ data: { project_path: "#{terminal_namespace_project_environment_path(@project.namespace, @project, @environment)}.ws" } }
+  #terminal{ data: { project_path: "#{terminal_project_environment_path(@project, @environment)}.ws" } }
diff --git a/app/views/projects/find_file/show.html.haml b/app/views/projects/find_file/show.html.haml
index 8a409541fe5890ab2587614573dd032db8593c96..e3bf48ee47f7e3d78b6366134a8ce56dcc8441c2 100644
--- a/app/views/projects/find_file/show.html.haml
+++ b/app/views/projects/find_file/show.html.haml
@@ -7,7 +7,7 @@
       = render 'shared/ref_switcher', destination: 'find_file', path: @path
     %ul.breadcrumb.repo-breadcrumb
       %li
-        = link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
+        = link_to project_tree_path(@project, @ref) do
           = @project.path
       %li.file-finder
         %input#file_find.form-control.file-finder-input{ type: "text", placeholder: _('Find by path'), autocomplete: 'off' }
@@ -20,8 +20,8 @@
 
 :javascript
   var projectFindFile = new ProjectFindFile($(".file-finder-holder"), {
-    url: "#{escape_javascript(namespace_project_files_path(@project.namespace, @project, @ref, @options.merge(format: :json)))}",
-    treeUrl: "#{escape_javascript(namespace_project_tree_path(@project.namespace, @project, @ref))}",
-    blobUrlTemplate: "#{escape_javascript(namespace_project_blob_path(@project.namespace, @project, @id || @commit.id))}"
+    url: "#{escape_javascript(project_files_path(@project, @ref, @options.merge(format: :json)))}",
+    treeUrl: "#{escape_javascript(project_tree_path(@project, @ref))}",
+    blobUrlTemplate: "#{escape_javascript(project_blob_path(@project, @id || @commit.id))}"
   });
   new ShortcutsFindFile(projectFindFile);
diff --git a/app/views/projects/forks/error.html.haml b/app/views/projects/forks/error.html.haml
index 524b77783ef1dc9b1ee5916076fd9c2835d87fe9..d365bcd4ecc7f19e937d0681743f165a04f1edee 100644
--- a/app/views/projects/forks/error.html.haml
+++ b/app/views/projects/forks/error.html.haml
@@ -20,6 +20,6 @@
           = error
 
     %p
-      = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork", class: "btn" do
+      = link_to new_project_fork_path(@project), title: "Fork", class: "btn" do
         %i.fa.fa-code-fork
         Try to fork again
diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml
index f4aa523b32d9e263cf810b0ce1324e1ea491fd0c..111cbcda2665d9d282e724a4ad0f047ca40855c1 100644
--- a/app/views/projects/forks/index.html.haml
+++ b/app/views/projects/forks/index.html.haml
@@ -34,7 +34,7 @@
           = custom_icon('icon_fork')
           %span Fork
       - else
-        = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn btn-new' do
+        = link_to new_project_fork_path(@project), title: "Fork project", class: 'btn btn-new' do
           = custom_icon('icon_fork')
           %span Fork
 
diff --git a/app/views/projects/forks/new.html.haml b/app/views/projects/forks/new.html.haml
index 5242bc72b716d4eb40489668a2e3fa87911d6b8d..0f36e1a7353be25557b8c2e9389619a723cfb46e 100644
--- a/app/views/projects/forks/new.html.haml
+++ b/app/views/projects/forks/new.html.haml
@@ -30,7 +30,7 @@
                         = namespace.human_name
                 - else
                   .fork-thumbnail
-                    = link_to namespace_project_forks_path(@project.namespace, @project, namespace_key: namespace.id), method: "POST" do
+                    = link_to project_forks_path(@project, namespace_key: namespace.id), method: "POST" do
                       - if /no_((\w*)_)*avatar/.match(avatar)
                         .no-avatar
                           = icon 'question'
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 b23bbadbdb43a05944533f02df3e19f6e3ca72cf..b98dc09534f179a98e61232a3294ea79be77ef5f 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
@@ -20,14 +20,14 @@
       - if generic_commit_status.ref
         .icon-container
           = generic_commit_status.tags.any? ? icon('tag') : icon('code-fork')
-        = link_to generic_commit_status.ref, namespace_project_commits_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.ref)
+        = link_to generic_commit_status.ref, project_commits_path(generic_commit_status.project, generic_commit_status.ref)
       - else
         .light none
       .icon-container.commit-icon
         = custom_icon("icon_commit")
 
     - if commit_sha
-      = link_to generic_commit_status.short_sha, namespace_project_commit_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.sha), class: "commit-sha"
+      = link_to generic_commit_status.short_sha, project_commit_path(generic_commit_status.project, generic_commit_status.sha), class: "commit-sha"
 
     - if retried
       = icon('warning', class: 'text-warning has-tooltip', title: 'Status was retried.')
@@ -53,7 +53,7 @@
   - if admin
     %td
       - if generic_commit_status.project
-        = link_to generic_commit_status.project.name_with_namespace, admin_namespace_project_path(generic_commit_status.project.namespace, generic_commit_status.project)
+        = link_to generic_commit_status.project.name_with_namespace, admin_project_path(generic_commit_status.project)
     %td
       - if generic_commit_status.try(:runner)
         = runner_link(generic_commit_status.runner)
diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml
index 680f8ae6c8f9b32aafb683757e1561210e37340d..640e0d689ca3cd80c13675d8cfbbde7a261b8510 100644
--- a/app/views/projects/graphs/show.html.haml
+++ b/app/views/projects/graphs/show.html.haml
@@ -35,7 +35,7 @@
 :javascript
   $.ajax({
     type: "GET",
-    url: "#{namespace_project_graph_path(@project.namespace, @project, current_ref, format: :json)}",
+    url: "#{project_graph_path(@project, current_ref, format: :json)}",
     dataType: "json",
     success: function (data) {
       var graph = new ContributorsStatGraph();
diff --git a/app/views/projects/hook_logs/_index.html.haml b/app/views/projects/hook_logs/_index.html.haml
index 6962b22345104ec393c0ed1d0c42e2bbbd0692ee..05b06cfc8b292070f853130d877590d2418ab7bb 100644
--- a/app/views/projects/hook_logs/_index.html.haml
+++ b/app/views/projects/hook_logs/_index.html.haml
@@ -28,7 +28,7 @@
             %td.light
               = time_ago_with_tooltip(hook_log.created_at)
             %td
-              = link_to 'View details', namespace_project_hook_hook_log_path(project.namespace, project, hook, hook_log)
+              = link_to 'View details', project_hook_hook_log_path(project, hook, hook_log)
 
       = paginate hook_logs, theme: 'gitlab'
 
diff --git a/app/views/projects/hook_logs/show.html.haml b/app/views/projects/hook_logs/show.html.haml
index 2eabe92f8eb491fe936f6a60194e5668fd220c76..ab5a7b117d78844055af662cd29fe1d95235ff2d 100644
--- a/app/views/projects/hook_logs/show.html.haml
+++ b/app/views/projects/hook_logs/show.html.haml
@@ -6,6 +6,6 @@
       Request details
   .col-lg-9
 
-    = link_to 'Resend Request', retry_namespace_project_hook_hook_log_path(@project.namespace, @project, @hook, @hook_log), class: "btn btn-default pull-right prepend-left-10"
+    = link_to 'Resend Request', retry_project_hook_hook_log_path(@project, @hook, @hook_log), class: "btn btn-default pull-right prepend-left-10"
 
     = render partial: 'shared/hook_logs/content', locals: { hook_log: @hook_log }
diff --git a/app/views/projects/hooks/_index.html.haml b/app/views/projects/hooks/_index.html.haml
index 676b7c345bca61f5b025679503d7a9a33785a046..776681ea09a90b391add41c2d8fb315d8e75038f 100644
--- a/app/views/projects/hooks/_index.html.haml
+++ b/app/views/projects/hooks/_index.html.haml
@@ -1,12 +1,12 @@
 .row.prepend-top-default
-  .col-lg-3
+  .col-lg-4
     %h4.prepend-top-0
       = page_title
     %p
       #{link_to 'Webhooks', help_page_path('user/project/integrations/webhooks')} can be
       used for binding events when something is happening within the project.
 
-  .col-lg-9.append-bottom-default
+  .col-lg-8.append-bottom-default
     = form_for @hook, as: :hook, url: polymorphic_path([@project.namespace.becomes(Namespace), @project, :hooks]) do |f|
       = render partial: 'shared/web_hooks/form', locals: { form: f, hook: @hook }
       = f.submit 'Add webhook', class: 'btn btn-create'
diff --git a/app/views/projects/hooks/edit.html.haml b/app/views/projects/hooks/edit.html.haml
index fd382c1d63fc2c0f20bc86a38f630cfa7f2964d6..4944e0c8041e22f1c0e25c422d35358f7eb47bfc 100644
--- a/app/views/projects/hooks/edit.html.haml
+++ b/app/views/projects/hooks/edit.html.haml
@@ -9,14 +9,13 @@
       #{link_to 'Webhooks', help_page_path('user/project/integrations/webhooks')} can be
       used for binding events when something is happening within the project.
   .col-lg-9.append-bottom-default
-    = form_for [@project.namespace.becomes(Namespace), @project, @hook], as: :hook, url: namespace_project_hook_path do |f|
+    = form_for [@project.namespace.becomes(Namespace), @project, @hook], as: :hook, url: project_hook_path(@project, @hook) do |f|
       = render partial: 'shared/web_hooks/form', locals: { form: f, hook: @hook }
 
       = f.submit 'Save changes', class: 'btn btn-create'
-      = link_to 'Test hook', test_namespace_project_hook_path(@project.namespace, @project, @hook), class: 'btn btn-default'
-      = link_to 'Remove', namespace_project_hook_path(@project.namespace, @project, @hook), method: :delete, class: 'btn btn-remove pull-right', data: { confirm: 'Are you sure?' }
+      = link_to 'Test hook', test_project_hook_path(@project, @hook), class: 'btn btn-default'
+      = link_to 'Remove', project_hook_path(@project, @hook), method: :delete, class: 'btn btn-remove pull-right', data: { confirm: 'Are you sure?' }
 
 %hr
 
 = render partial: 'projects/hook_logs/index', locals: { hook: @hook, hook_logs: @hook_logs, project: @project }
-
diff --git a/app/views/projects/imports/new.html.haml b/app/views/projects/imports/new.html.haml
index 25a87411cac64e168745ae8388890addca14baee..778ff91362d47bba5d5a6a04c48e63c039a3e654 100644
--- a/app/views/projects/imports/new.html.haml
+++ b/app/views/projects/imports/new.html.haml
@@ -12,7 +12,7 @@
         :preserve
           #{h(sanitize_repo_path(@project, @project.import_error))}
 
-= form_for @project, url: namespace_project_import_path(@project.namespace, @project), method: :post, html: { class: 'form-horizontal' } do |f|
+= form_for @project, url: project_import_path(@project), method: :post, html: { class: 'form-horizontal' } do |f|
   = render "shared/import_form", f: f
 
   .form-actions
diff --git a/app/views/projects/issues/_head.html.haml b/app/views/projects/issues/_head.html.haml
index 7a188cb64459cd3e657088b7cef14c062f014e8c..e9f21594a712e4ed85d307994d1977ef8b8e1ce8 100644
--- a/app/views/projects/issues/_head.html.haml
+++ b/app/views/projects/issues/_head.html.haml
@@ -5,29 +5,29 @@
       %ul{ class: (container_class) }
         - if project_nav_tab?(:issues) && !current_controller?(:merge_requests)
           = nav_link(controller: :issues) do
-            = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues' do
+            = link_to project_issues_path(@project), title: 'Issues' do
               %span
                 List
 
           = nav_link(controller: :boards) do
-            = link_to namespace_project_boards_path(@project.namespace, @project), title: 'Board' do
+            = link_to project_boards_path(@project), title: 'Board' do
               %span
                 Board
 
         - if project_nav_tab?(:merge_requests) && current_controller?(:merge_requests)
           = nav_link(controller: :merge_requests) do
-            = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests' do
+            = link_to project_merge_requests_path(@project), title: 'Merge Requests' do
               %span
                 Merge Requests
 
         - if project_nav_tab? :labels
           = nav_link(controller: :labels) do
-            = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do
+            = link_to project_labels_path(@project), title: 'Labels' do
               %span
                 Labels
 
         - if project_nav_tab? :milestones
           = nav_link(controller: :milestones) do
-            = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do
+            = link_to project_milestones_path(@project), title: 'Milestones' do
               %span
                 Milestones
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index 9e4e6934ca978322ff3f93de0f1976e8cefbee3f..7dc35be57a6161c07ed301a537f8438838cb24b2 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -4,43 +4,49 @@
       .issue-check.hidden
         = check_box_tag dom_id(issue, "selected"), nil, false, 'data-id' => issue.id, class: "selected_issue"
     .issue-info-container
-      .issue-title.title
-        %span.issue-title-text
-          = confidential_icon(issue)
-          = link_to issue.title, issue_path(issue)
+      .issue-main-info
+        .issue-title.title
+          %span.issue-title-text
+            = confidential_icon(issue)
+            = link_to issue.title, issue_path(issue)
+          - if issue.tasks?
+            %span.task-status.hidden-xs
+              &nbsp;
+              = issue.task_status
+
+        .issuable-info
+          %span.issuable-reference
+            #{issuable_reference(issue)}
+          %span.issuable-authored.hidden-xs
+            &middot;
+            opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')}
+            by #{link_to_member(@project, issue.author, avatar: false)}
+          - if issue.milestone
+            %span.issuable-milestone.hidden-xs
+              &nbsp;
+              = link_to project_issues_path(issue.project, milestone_title: issue.milestone.title) do
+                = icon('clock-o')
+                = issue.milestone.title
+          - if issue.due_date
+            %span.issuable-due-date.hidden-xs{ class: "#{'cred' if issue.overdue?}" }
+              &nbsp;
+              = icon('calendar')
+              = issue.due_date.to_s(:medium)
+          - if issue.labels.any?
+            &nbsp;
+            - issue.labels.each do |label|
+              = link_to_label(label, subject: issue.project, css_class: 'label-link')
+
+      .issuable-meta
         %ul.controls
           - if issue.closed?
-            %li
+            %li.issuable-status
               CLOSED
-
           - if issue.assignees.any?
             %li
               = render 'shared/issuable/assignees', project: @project, issue: issue
 
           = render 'shared/issuable_meta_data', issuable: issue
 
-      .issue-info
-        #{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
-          &nbsp;
-          = link_to namespace_project_issues_path(issue.project.namespace, issue.project, milestone_title: issue.milestone.title) do
-            = icon('clock-o')
-            = issue.milestone.title
-        - if issue.due_date
-          %span{ class: "#{'cred' if issue.overdue?}" }
-            &nbsp;
-            = icon('calendar')
-            = issue.due_date.to_s(:medium)
-        - if issue.labels.any?
-          &nbsp;
-          - issue.labels.each do |label|
-            = link_to_label(label, subject: issue.project, css_class: 'label-link')
-        - if issue.tasks?
-          &nbsp;
-          %span.task-status
-            = issue.task_status
-
-        .pull-right.issue-updated-at
+        .pull-right.issuable-updated-at.hidden-xs
           %span updated #{time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_update_ago')}
diff --git a/app/views/projects/issues/_issue_by_email.html.haml b/app/views/projects/issues/_issue_by_email.html.haml
index da65157a10b71e979a359e38b11637d29f897b3d..264032a3a31b8b0308edc6df7ad1f529e8a3e3f1 100644
--- a/app/views/projects/issues/_issue_by_email.html.haml
+++ b/app/views/projects/issues/_issue_by_email.html.haml
@@ -20,7 +20,7 @@
         %p
           The subject will be used as the title of the new issue, and the message will be the description.
 
-          = link_to 'Slash commands', help_page_path('user/project/slash_commands'), target: '_blank', tabindex: -1
+          = link_to 'Quick actions', help_page_path('user/project/quick_actions'), target: '_blank', tabindex: -1
           and styling with
           = link_to 'Markdown', help_page_path('user/markdown'), target: '_blank', tabindex: -1
           are supported.
@@ -30,5 +30,5 @@
 
           Anyone who gets ahold of it can create issues as if they were you.
           You should
-          = link_to 'reset it', new_issue_address_namespace_project_path(@project.namespace, @project), class: 'incoming-email-token-reset'
+          = link_to 'reset it', new_issue_address_project_path(@project), class: 'incoming-email-token-reset'
           if that ever happens.
diff --git a/app/views/projects/issues/_merge_requests.html.haml b/app/views/projects/issues/_merge_requests.html.haml
index d48923b422a3e12b0f7f04e461c75c9efa5bf20c..6a5674875143319d7a6c34c6931255b21469ad4b 100644
--- a/app/views/projects/issues/_merge_requests.html.haml
+++ b/app/views/projects/issues/_merge_requests.html.haml
@@ -14,11 +14,11 @@
           = merge_request.to_reference
         %span.merge-request-info
           %strong
-            = link_to_gfm merge_request.title, merge_request_path(merge_request), class: "row_title"
+            = link_to merge_request.title, merge_request_path(merge_request), class: "row_title"
           - unless @issue.project.id == merge_request.target_project.id
             in
             - project = merge_request.target_project
-            = link_to project.name_with_namespace, namespace_project_path(project.namespace, project)
+            = link_to project.name_with_namespace, project_path(project)
 
         - if merge_request.merged?
           %span.merge-request-status.prepend-left-10.merged
diff --git a/app/views/projects/issues/_nav_btns.html.haml b/app/views/projects/issues/_nav_btns.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..756faf4625ea9a74300f25666c78312cd6932957
--- /dev/null
+++ b/app/views/projects/issues/_nav_btns.html.haml
@@ -0,0 +1,10 @@
+= link_to params.merge(rss_url_options), class: 'btn btn-default append-right-10 has-tooltip', title: 'Subscribe' do
+  = icon('rss')
+- if @can_bulk_update
+  = button_tag "Edit Issues", class: "btn btn-default append-right-10 js-bulk-update-toggle"
+= link_to "New issue", new_project_issue_path(@project,
+                                              issue: { assignee_id: issues_finder.assignee.try(:id),
+                                                       milestone_id: issues_finder.milestones.first.try(:id) }),
+                                              class: "btn btn-new",
+                                              title: "New issue",
+                                              id: "new_issue_link"
diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml
index dba092c884461b7d42e9e0e7d2cf845cb37bc009..e1b4a49850af71102db1b7380c2f4330870240f1 100644
--- a/app/views/projects/issues/_new_branch.html.haml
+++ b/app/views/projects/issues/_new_branch.html.haml
@@ -1,5 +1,5 @@
 - if can?(current_user, :push_code, @project)
-  .create-mr-dropdown-wrap{ data: { can_create_path: can_create_branch_namespace_project_issue_path(@project.namespace, @project, @issue), create_mr_path: create_merge_request_namespace_project_issue_path(@project.namespace, @project, @issue), create_branch_path: namespace_project_branches_path(@project.namespace, @project, branch_name: @issue.to_branch_name, issue_iid: @issue.iid) } }
+  .create-mr-dropdown-wrap{ data: { can_create_path: can_create_branch_project_issue_path(@project, @issue), create_mr_path: create_merge_request_project_issue_path(@project, @issue), create_branch_path: project_branches_path(@project, branch_name: @issue.to_branch_name, issue_iid: @issue.iid) } }
     .btn-group.unavailable
       %button.btn.btn-grouped{ type: 'button', disabled: 'disabled' }
         = icon('spinner', class: 'fa-spin')
diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml
index 8c9f6f3b4df6078b4868049bec3bed34608f2834..1df38db9fd474b069cb1996783da7ad2fd1be9b3 100644
--- a/app/views/projects/issues/_related_branches.html.haml
+++ b/app/views/projects/issues/_related_branches.html.haml
@@ -11,4 +11,4 @@
             = render_pipeline_status(pipeline)
         %span.related-branch-info
           %strong
-            = link_to branch, namespace_project_compare_path(@project.namespace, @project, from: @project.default_branch, to: branch), class: "ref-name"
+            = link_to branch, project_compare_path(@project, from: @project.default_branch, to: branch), class: "ref-name"
diff --git a/app/views/projects/issues/index.atom.builder b/app/views/projects/issues/index.atom.builder
index 61346884346e0935ef38291a7c34345c9a6db0aa..4029926f373dcb286cc207dfcc1f85e7c3f1be90 100644
--- a/app/views/projects/issues/index.atom.builder
+++ b/app/views/projects/issues/index.atom.builder
@@ -1,7 +1,7 @@
 xml.title   "#{@project.name} issues"
 xml.link    href: url_for(params), rel: "self", type: "application/atom+xml"
-xml.link    href: namespace_project_issues_url(@project.namespace, @project), rel: "alternate", type: "text/html"
-xml.id      namespace_project_issues_url(@project.namespace, @project)
+xml.link    href: project_issues_url(@project), rel: "alternate", type: "text/html"
+xml.id      project_issues_url(@project)
 xml.updated @issues.first.updated_at.xmlschema if @issues.reorder(nil).any?
 
 xml << render(partial: 'issues/issue', collection: @issues) if @issues.reorder(nil).any?
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index 7183794ce7258adeba718f9bc582973315453c84..aacb057840d36873a2f73232ac8dba31d73c5fa7 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -13,23 +13,16 @@
 = content_for :meta_tags do
   = auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{@project.name} issues")
 
+- if show_new_nav?
+  - content_for :breadcrumbs_extra do
+    = render "projects/issues/nav_btns"
+
 - if project_issues(@project).exists?
   %div{ class: (container_class) }
     .top-area
       = render 'shared/issuable/nav', type: :issues
-      .nav-controls
-        = link_to params.merge(rss_url_options), class: 'btn append-right-10 has-tooltip', title: 'Subscribe' do
-          = icon('rss')
-        - if @can_bulk_update
-          = button_tag "Edit Issues", class: "btn btn-default js-bulk-update-toggle"
-        = link_to new_namespace_project_issue_path(@project.namespace,
-                                                   @project,
-                                                   issue: { assignee_id: issues_finder.assignee.try(:id),
-                                                            milestone_id: issues_finder.milestones.first.try(:id) }),
-                                                   class: "btn btn-new",
-                                                   title: "New issue",
-                                                   id: "new_issue_link" do
-          New issue
+      .nav-controls{ class: ("visible-xs" if show_new_nav?) }
+        = render "projects/issues/nav_btns"
     = render 'shared/issuable/search_bar', type: :issues
 
     - if @can_bulk_update
@@ -40,4 +33,4 @@
       - if new_issue_email
         = render 'issue_by_email', email: new_issue_email
 - else
-  = render 'shared/empty_states/issues', button_path: new_namespace_project_issue_path(@project.namespace, @project)
+  = render 'shared/empty_states/issues', button_path: new_project_issue_path(@project)
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 5f92d020eef08820e43a2764bd0d6c3a5191c3be..cf8493faba8d7128ad6485cfe017a8940255a9b4 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -5,13 +5,6 @@
 - can_update_issue = can?(current_user, :update_issue, @issue)
 - can_report_spam = @issue.submittable_as_spam_by?(current_user)
 
-- if defined?(@issue) && @issue.confidential?
-  .confidential-issue-warning{ data: { spy: 'affix' } }
-    %span.confidential-issue-text
-      #{confidential_icon(@issue)} This issue is confidential.
-      %a{ href: help_page_path('user/project/issues/confidential_issues'), target: '_blank' }
-        What are confidential issues?
-
 .clearfix.detail-page-header
   .issuable-header
     .issuable-status-box.status-box.status-box-closed{ class: issue_button_visibility(@issue, false) }
@@ -26,6 +19,7 @@
       = icon('angle-double-left')
 
     .issuable-meta
+      = confidential_icon(@issue)
       = issuable_meta(@issue, @project, "Issue")
 
   .issuable-actions
@@ -37,26 +31,26 @@
         %ul
           - if can_update_issue
             %li
-              = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'issuable-edit'
+              = link_to 'Edit', edit_project_issue_path(@project, @issue), class: 'issuable-edit'
             %li
               = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
             %li
               = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), class: "btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
           - if can_report_spam
             %li
-              = link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'btn-spam', title: 'Submit as spam'
+              = link_to 'Submit as spam', mark_as_spam_project_issue_path(@project, @issue), method: :post, class: 'btn-spam', title: 'Submit as spam'
           - if can_update_issue || can_report_spam
             %li.divider
           %li
-            = link_to 'New issue', new_namespace_project_issue_path(@project.namespace, @project), title: 'New issue', id: 'new_issue_link'
+            = link_to 'New issue', new_project_issue_path(@project), title: 'New issue', id: 'new_issue_link'
 
     - if can_update_issue
-      = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped issuable-edit'
+      = link_to 'Edit', edit_project_issue_path(@project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped issuable-edit'
       = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), class: "hidden-xs hidden-sm btn btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
       = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), class: "hidden-xs hidden-sm btn btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
     - if can_report_spam
-      = link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'hidden-xs hidden-sm btn btn-grouped btn-spam', title: 'Submit as spam'
-    = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'hidden-xs hidden-sm btn btn-grouped new-issue-link btn-new btn-inverted', title: 'New issue', id: 'new_issue_link' do
+      = link_to 'Submit as spam', mark_as_spam_project_issue_path(@project, @issue), method: :post, class: 'hidden-xs hidden-sm btn btn-grouped btn-spam', title: 'Submit as spam'
+    = link_to new_project_issue_path(@project), class: 'hidden-xs hidden-sm btn btn-grouped new-issue-link btn-new btn-inverted', title: 'New issue', id: 'new_issue_link' do
       New issue
 
 .issue-details.issuable-details
@@ -71,10 +65,10 @@
 
       = edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue-edited-ago js-issue-edited-ago')
 
-    #merge-requests{ data: { url: referenced_merge_requests_namespace_project_issue_url(@project.namespace, @project, @issue) } }
+    #merge-requests{ data: { url: referenced_merge_requests_project_issue_url(@project, @issue) } }
       // This element is filled in using JavaScript.
 
-    #related-branches{ data: { url: related_branches_namespace_project_issue_url(@project.namespace, @project, @issue) } }
+    #related-branches{ data: { url: related_branches_project_issue_url(@project, @issue) } }
       // This element is filled in using JavaScript.
 
   .content-block.emoji-block
diff --git a/app/views/projects/jobs/_header.html.haml b/app/views/projects/jobs/_header.html.haml
index ad72ab5b199b67332962b8fbe84674332a760048..d81b8f6bb4c0d538b6c8cad28d6369d60b042140 100644
--- a/app/views/projects/jobs/_header.html.haml
+++ b/app/views/projects/jobs/_header.html.haml
@@ -6,13 +6,13 @@
     = render 'ci/status/badge', status: @build.detailed_status(current_user), link: false, title: @build.status_title
     %strong
       Job
-      = link_to "##{@build.id}", namespace_project_job_path(@project.namespace, @project, @build), class: 'js-build-id'
+      = link_to "##{@build.id}", project_job_path(@project, @build), class: 'js-build-id'
     in pipeline
     %strong
       = link_to "##{pipeline.id}", pipeline_path(pipeline)
     for
     %strong
-      = link_to pipeline.short_sha, namespace_project_commit_path(@project.namespace, @project, pipeline.sha), class: 'commit-sha'
+      = link_to pipeline.short_sha, project_commit_path(@project, pipeline.sha), class: 'commit-sha'
     from
     %strong
       = link_to @build.ref, project_ref_path(@project, @build.ref), class: 'ref-name'
@@ -24,8 +24,8 @@
   - if show_controls
     .nav-controls
       - if can?(current_user, :create_issue, @project) && @build.failed?
-        = link_to "New issue", new_namespace_project_issue_path(@project.namespace, @project, issue: build_failed_issue_options), class: 'btn btn-new btn-inverted'
+        = link_to "New issue", new_project_issue_path(@project, issue: build_failed_issue_options), class: 'btn btn-new btn-inverted'
       - if can?(current_user, :update_build, @build) && @build.retryable?
-        = link_to "Retry job", retry_namespace_project_job_path(@project.namespace, @project, @build), class: 'btn btn-inverted-secondary', method: :post
+        = link_to "Retry job", retry_project_job_path(@project, @build), class: 'btn btn-inverted-secondary', 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/jobs/_sidebar.html.haml b/app/views/projects/jobs/_sidebar.html.haml
index 93e8a4e385ccbf70dd04e3272e9d0ae342821030..bddb587ddc6378125b79e3557835eb879cb3f71e 100644
--- a/app/views/projects/jobs/_sidebar.html.haml
+++ b/app/views/projects/jobs/_sidebar.html.haml
@@ -26,14 +26,14 @@
         - if @build.artifacts?
           .btn-group.btn-group-justified{ role: :group }
             - if @build.has_expiring_artifacts? && can?(current_user, :update_build, @build)
-              = link_to keep_namespace_project_job_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default', method: :post do
+              = link_to keep_project_job_artifacts_path(@project, @build), class: 'btn btn-sm btn-default', method: :post do
                 Keep
 
-            = link_to download_namespace_project_job_artifacts_path(@project.namespace, @project, @build), rel: 'nofollow', download: '', class: 'btn btn-sm btn-default' do
+            = link_to download_project_job_artifacts_path(@project, @build), rel: 'nofollow', download: '', class: 'btn btn-sm btn-default' do
               Download
 
             - if @build.artifacts_metadata?
-              = link_to browse_namespace_project_job_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default' do
+              = link_to browse_project_job_artifacts_path(@project, @build), class: 'btn btn-sm btn-default' do
                 Browse
 
     - if @build.trigger_request
@@ -58,7 +58,7 @@
     .block
       %p
         Commit
-        = link_to @build.pipeline.short_sha, namespace_project_commit_path(@project.namespace, @project, @build.pipeline.sha), class: 'commit-sha link-commit'
+        = link_to @build.pipeline.short_sha, project_commit_path(@project, @build.pipeline.sha), class: 'commit-sha link-commit'
         = clipboard_button(text: @build.pipeline.short_sha, title: "Copy commit SHA to clipboard")
         - if @build.merge_request
           in
@@ -73,9 +73,9 @@
           %span{ class: "ci-status-icon-#{@build.pipeline.status}" }
             = ci_icon_for_status(@build.pipeline.status)
           Pipeline
-          = link_to "##{@build.pipeline.id}", namespace_project_pipeline_path(@project.namespace, @project, @build.pipeline), class: 'link-commit'
+          = link_to "##{@build.pipeline.id}", project_pipeline_path(@project, @build.pipeline), class: 'link-commit'
           from
-          = link_to "#{@build.pipeline.ref}", namespace_project_branch_path(@project.namespace, @project, @build.pipeline.ref), class: 'link-commit'
+          = link_to "#{@build.pipeline.ref}", project_branch_path(@project, @build.pipeline.ref), class: 'link-commit'
         %button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
           %span.stage-selection More
           = icon('chevron-down')
@@ -88,7 +88,7 @@
     - HasStatus::ORDERED_STATUSES.each do |build_status|
       - builds.select{|build| build.status == build_status}.each do |build|
         .build-job{ class: sidebar_build_class(build, @build), data: { stage: build.stage } }
-          = link_to namespace_project_job_path(@project.namespace, @project, build) do
+          = link_to project_job_path(@project, build) do
             = icon('arrow-right')
             %span{ class: "ci-status-icon-#{build.status}" }
               = ci_icon_for_status(build.status)
diff --git a/app/views/projects/jobs/index.html.haml b/app/views/projects/jobs/index.html.haml
index a33e3978ee12df71680b74043be58e0637713840..8604c7d3ea407958831fcf7853e7fc0a297d9e21 100644
--- a/app/views/projects/jobs/index.html.haml
+++ b/app/views/projects/jobs/index.html.haml
@@ -10,7 +10,7 @@
     .nav-controls
       - if can?(current_user, :update_build, @project)
         - if @all_builds.running_or_pending.any?
-          = link_to 'Cancel running', cancel_all_namespace_project_jobs_path(@project.namespace, @project),
+          = link_to 'Cancel running', cancel_all_project_jobs_path(@project),
             data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
 
         - unless @repository.gitlab_ci_yml
diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml
index c73bae0a2c939069a8774e1a080c73086fba66db..fa086413fbe9d9e73f96c8b0b3e9102a7812bd46 100644
--- a/app/views/projects/jobs/show.html.haml
+++ b/app/views/projects/jobs/show.html.haml
@@ -21,7 +21,7 @@
 
             %br
             Go to
-            = link_to namespace_project_runners_path(@build.project.namespace, @build.project) do
+            = link_to project_runners_path(@build.project) do
               Runners page
 
     - if @build.starts_environment?
@@ -54,23 +54,24 @@
           - else
             Job has been erased #{time_ago_with_tooltip(@build.erased_at)}
 
-    .build-trace-container#build-trace
-      .top-bar.sticky
+    .build-trace-container.prepend-top-default
+      .top-bar.js-top-bar
         .js-truncated-info.truncated-info.hidden<
           Showing last
           %span.js-truncated-info-size.truncated-info-size><
           KiB of log -
-          %a.js-raw-link.raw-link{ href: raw_namespace_project_job_path(@project.namespace, @project, @build) }>< Complete Raw
+          %a.js-raw-link.raw-link{ href: raw_project_job_path(@project, @build) }>< Complete Raw
+
         .controllers
           - if @build.has_trace?
-            = link_to raw_namespace_project_job_path(@project.namespace, @project, @build),
+            = link_to raw_project_job_path(@project, @build),
                     title: 'Show complete raw',
                     data: { placement: 'top', container: 'body' },
                     class: 'js-raw-link-controller has-tooltip controllers-buttons' do
               = icon('file-text-o')
 
           - if can?(current_user, :update_build, @project) && @build.erasable?
-            = link_to erase_namespace_project_job_path(@project.namespace, @project, @build),
+            = link_to erase_project_job_path(@project, @build),
                       method: :post,
                       data: { confirm: 'Are you sure you want to erase this build?', placement: 'top', container: 'body' },
                       title: 'Erase job log',
@@ -82,15 +83,17 @@
           .has-tooltip.controllers-buttons{ title: 'Scroll to bottom', data: { placement: 'top', container: 'body'} }
             %button.js-scroll-down.btn-scroll.btn-transparent.btn-blank{ type: 'button', disabled: true }
               = custom_icon('scroll_down')
-      .bash.sticky.js-scroll-container
-        %code.js-build-output
+
+      %pre.build-trace#build-trace
+        %code.bash.js-build-output
         .build-loader-animation.js-build-refresh
 
+
   = render "sidebar"
 
 .js-build-options{ data: javascript_build_options }
 
-#js-job-details-vue{ data: { endpoint: namespace_project_job_path(@project.namespace, @project, @build, format: :json) } }
+#js-job-details-vue{ data: { endpoint: project_job_path(@project, @build, format: :json) } }
 
 - content_for :page_specific_javascripts do
   = webpack_bundle_tag('common_vue')
diff --git a/app/views/projects/labels/edit.html.haml b/app/views/projects/labels/edit.html.haml
index 7f0059cdcda05e51623dc869782835957e5dd03c..84b0b65d1c093637e4c496088635f575febc6020 100644
--- a/app/views/projects/labels/edit.html.haml
+++ b/app/views/projects/labels/edit.html.haml
@@ -6,4 +6,4 @@
   %h3.page-title
     Edit Label
   %hr
-  = render 'shared/labels/form', url: namespace_project_label_path(@project.namespace.becomes(Namespace), @project, @label), back_path: namespace_project_labels_path(@project.namespace, @project)
+  = render 'shared/labels/form', url: project_label_path(@project, @label), back_path: project_labels_path(@project)
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index fc72c4fb635d4d7cfe0242b9ad9a6416d2eb7eeb..8fbc4588902c33b06333bc16822f869ce77bc6b3 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -11,7 +11,7 @@
 
       .nav-controls
         - if can?(current_user, :admin_label, @project)
-          = link_to new_namespace_project_label_path(@project.namespace, @project), class: "btn btn-new" do
+          = link_to new_project_label_path(@project), class: "btn btn-new" do
             New label
 
     .labels
@@ -20,7 +20,7 @@
         - 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) }
+          %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_project_labels_path(@project) }
             #js-priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty?}" }
               = render 'shared/empty_states/priority_labels'
             - if @prioritized_labels.present?
diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml
index 8f6c085a361dfb303388569028f6df663995e241..79e90b7ca3be4783e2b2680a6da29d35160087c3 100644
--- a/app/views/projects/labels/new.html.haml
+++ b/app/views/projects/labels/new.html.haml
@@ -6,4 +6,4 @@
   %h3.page-title
     New Label
   %hr
-  = render 'shared/labels/form', url: namespace_project_labels_path(@project.namespace.becomes(Namespace), @project), back_path: namespace_project_labels_path(@project.namespace, @project)
+  = render 'shared/labels/form', url: project_labels_path(@project), back_path: project_labels_path(@project)
diff --git a/app/views/projects/mattermosts/_no_teams.html.haml b/app/views/projects/mattermosts/_no_teams.html.haml
index aac74a25b7549b0149fdc1a6b40e934877b8ddd8..243dcfdc187d3c7acaa4becafe6c0a71dae5ad69 100644
--- a/app/views/projects/mattermosts/_no_teams.html.haml
+++ b/app/views/projects/mattermosts/_no_teams.html.haml
@@ -13,4 +13,4 @@
   and try again.
 %hr
 .clearfix
-  = link_to 'Go back', edit_namespace_project_service_path(@project.namespace, @project, @service), class: 'btn btn-lg pull-right'
+  = link_to 'Go back', edit_project_service_path(@project, @service), class: 'btn btn-lg pull-right'
diff --git a/app/views/projects/mattermosts/_team_selection.html.haml b/app/views/projects/mattermosts/_team_selection.html.haml
index 04bd4e8b6830e8f18c392dc67254f9bcc07c83dc..3bdb5d0adc4948911111063c2f40377752935172 100644
--- a/app/views/projects/mattermosts/_team_selection.html.haml
+++ b/app/views/projects/mattermosts/_team_selection.html.haml
@@ -2,7 +2,7 @@
   This service will be installed on the Mattermost instance at
   %strong= link_to Gitlab.config.mattermost.host, Gitlab.config.mattermost.host
 %hr
-= form_for(:mattermost, method: :post, url: namespace_project_mattermost_path(@project.namespace, @project), html: { class: 'js-requires-input'} ) do |f|
+= form_for(:mattermost, method: :post, url: project_mattermost_path(@project), html: { class: 'js-requires-input'} ) do |f|
   %h4 Team
   %p
     = @teams.one? ? 'The team' : 'Select the team'
@@ -42,5 +42,5 @@
   %hr
   .clearfix
     .pull-right
-      = link_to 'Cancel', edit_namespace_project_service_path(@project.namespace, @project, @service), class: 'btn btn-lg'
+      = link_to 'Cancel', edit_project_service_path(@project, @service), class: 'btn btn-lg'
       = f.submit 'Install', class: 'btn btn-save btn-lg'
diff --git a/app/views/projects/merge_requests/show/_commits.html.haml b/app/views/projects/merge_requests/_commits.html.haml
similarity index 100%
rename from app/views/projects/merge_requests/show/_commits.html.haml
rename to app/views/projects/merge_requests/_commits.html.haml
diff --git a/app/views/projects/merge_requests/_head.html.haml b/app/views/projects/merge_requests/_head.html.haml
index b7f73fe5339dd6efba89ca9f663c93ea6ef086e6..1e505222887cd773e2d0786eb36edf9a0e304ee9 100644
--- a/app/views/projects/merge_requests/_head.html.haml
+++ b/app/views/projects/merge_requests/_head.html.haml
@@ -4,18 +4,18 @@
     .nav-links.sub-nav.scrolling-tabs
       %ul{ class: (container_class) }
         = nav_link(controller: :merge_requests) do
-          = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests' do
+          = link_to project_merge_requests_path(@project), title: 'Merge Requests' do
             %span
               List
 
         - if project_nav_tab? :labels
           = nav_link(controller: :labels) do
-            = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do
+            = link_to project_labels_path(@project), title: 'Labels' do
               %span
                 Labels
 
         - if project_nav_tab? :milestones
           = nav_link(controller: :milestones) do
-            = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do
+            = link_to project_milestones_path(@project), title: 'Milestones' do
               %span
                 Milestones
diff --git a/app/views/projects/merge_requests/show/_how_to_merge.html.haml b/app/views/projects/merge_requests/_how_to_merge.html.haml
similarity index 100%
rename from app/views/projects/merge_requests/show/_how_to_merge.html.haml
rename to app/views/projects/merge_requests/_how_to_merge.html.haml
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index c13110deb16df48e7261e711ba6c7fe5e6493736..0a1ebcb8124803debbf2d3f9a030a468bc60a9ce 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -4,58 +4,60 @@
       = check_box_tag dom_id(merge_request, "selected"), nil, false, 'data-id' => merge_request.id, class: "selected_issue"
 
   .issue-info-container
-    .merge-request-title.title
-      %span.merge-request-title-text
-        = link_to merge_request.title, merge_request_path(merge_request)
+    .issue-main-info
+      .merge-request-title.title
+        %span.merge-request-title-text
+          = link_to merge_request.title, merge_request_path(merge_request)
+        - if merge_request.tasks?
+          %span.task-status.hidden-xs
+            &nbsp;
+            = merge_request.task_status
+
+      .issuable-info
+        %span.issuable-reference
+          #{issuable_reference(merge_request)}
+        %span.issuable-authored.hidden-xs
+          &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.milestone
+          %span.issuable-milestone.hidden-xs
+            &nbsp;
+            = link_to project_merge_requests_path(merge_request.project, milestone_title: merge_request.milestone.title) do
+              = icon('clock-o')
+              = merge_request.milestone.title
+        - if merge_request.target_project.default_branch != merge_request.target_branch
+          %span.project-ref-path
+            &nbsp;
+            = link_to project_ref_path(merge_request.project, merge_request.target_branch), class: 'ref-name' do
+              = icon('code-fork')
+              = merge_request.target_branch
+        - if merge_request.labels.any?
+          &nbsp;
+          - merge_request.labels.each do |label|
+            = link_to_label(label, subject: merge_request.project, type: :merge_request, css_class: 'label-link')
+
+    .issuable-meta
       %ul.controls
         - if merge_request.merged?
-          %li
+          %li.issuable-status.hidden-xs
             MERGED
         - elsif merge_request.closed?
-          %li
+          %li.issuable-status.hidden-xs
             = icon('ban')
             CLOSED
-
         - if merge_request.head_pipeline
-          %li
+          %li.issuable-pipeline-status.hidden-xs
             = render_pipeline_status(merge_request.head_pipeline)
-
         - if merge_request.open? && merge_request.broken?
-          %li
+          %li.issuable-pipeline-broken.hidden-xs
             = link_to merge_request_path(merge_request), class: "has-tooltip", title: "Cannot be merged automatically", data: { container: 'body' } do
               = icon('exclamation-triangle')
-
         - if merge_request.assignee
           %li
             = link_to_member(merge_request.source_project, merge_request.assignee, name: false, title: "Assigned to :name")
 
         = render 'shared/issuable_meta_data', issuable: merge_request
 
-    .merge-request-info
-      #{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
-        &nbsp;
-        = link_to project_ref_path(merge_request.project, merge_request.target_branch), class: 'ref-name' do
-          = icon('code-fork')
-          = merge_request.target_branch
-
-      - if merge_request.milestone
-        &nbsp;
-        = link_to namespace_project_merge_requests_path(merge_request.project.namespace, merge_request.project, milestone_title: merge_request.milestone.title) do
-          = icon('clock-o')
-          = merge_request.milestone.title
-
-      - if merge_request.labels.any?
-        &nbsp;
-        - merge_request.labels.each do |label|
-          = link_to_label(label, subject: merge_request.project, type: :merge_request, css_class: 'label-link')
-
-      - if merge_request.tasks?
-        &nbsp;
-        %span.task-status
-          = merge_request.task_status
-
-      .pull-right.hidden-xs
+      .pull-right.issuable-updated-at.hidden-xs
         %span updated #{time_ago_with_tooltip(merge_request.updated_at, placement: 'bottom', html_class: 'merge_request_updated_ago')}
diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/_mr_box.html.haml
similarity index 100%
rename from app/views/projects/merge_requests/show/_mr_box.html.haml
rename to app/views/projects/merge_requests/_mr_box.html.haml
diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/_mr_title.html.haml
similarity index 86%
rename from app/views/projects/merge_requests/show/_mr_title.html.haml
rename to app/views/projects/merge_requests/_mr_title.html.haml
index d9428b8562e16f37c3d84acec8bfe0a9726e43cc..3182aecd0a8d9e888c5633639cd98817e228844a 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/_mr_title.html.haml
@@ -28,8 +28,8 @@
             %li{ class: merge_request_button_visibility(@merge_request, false) }
               = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request'
             %li
-              = link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'issuable-edit'
+              = link_to 'Edit', edit_project_merge_request_path(@project, @merge_request), class: 'issuable-edit'
         = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "hidden-xs hidden-sm btn btn-grouped btn-close #{merge_request_button_visibility(@merge_request, true)}", title: 'Close merge request'
         = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "hidden-xs hidden-sm btn btn-grouped btn-reopen reopen-mr-link #{merge_request_button_visibility(@merge_request, false)}", title: 'Reopen merge request'
-        = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "hidden-xs hidden-sm btn btn-grouped issuable-edit" do
+        = link_to edit_project_merge_request_path(@project, @merge_request), class: "hidden-xs hidden-sm btn btn-grouped issuable-edit" do
           Edit
diff --git a/app/views/projects/merge_requests/_nav_btns.html.haml b/app/views/projects/merge_requests/_nav_btns.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..e92f27123473a3dfeb07a7b5197b3342a2da57f8
--- /dev/null
+++ b/app/views/projects/merge_requests/_nav_btns.html.haml
@@ -0,0 +1,5 @@
+- if @can_bulk_update
+  = button_tag "Edit Merge Requests", class: "btn js-bulk-update-toggle"
+- if merge_project
+  = link_to new_merge_request_path, class: "btn btn-new", title: "New merge request" do
+    New merge request
diff --git a/app/views/projects/merge_requests/show/_pipelines.html.haml b/app/views/projects/merge_requests/_pipelines.html.haml
similarity index 55%
rename from app/views/projects/merge_requests/show/_pipelines.html.haml
rename to app/views/projects/merge_requests/_pipelines.html.haml
index 2f1dbe87619431c39b3bb63fd4b32aca4c50ea5c..473b7b919c83948a34d6dff23099ebf7171ed9e0 100644
--- a/app/views/projects/merge_requests/show/_pipelines.html.haml
+++ b/app/views/projects/merge_requests/_pipelines.html.haml
@@ -1,4 +1,4 @@
-- endpoint_path = local_assigns[:endpoint] || pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, format: :json)
+- endpoint_path = local_assigns[:endpoint] || pipelines_project_merge_request_path(@project, @merge_request, format: :json)
 - disable_initialization = local_assigns.fetch(:disable_initialization, false)
 
 = render 'projects/commit/pipelines_list', endpoint: endpoint_path, disable_initialization: disable_initialization
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
deleted file mode 100644
index 75120409bb3d8b720a2bc78e048957255fac0ec9..0000000000000000000000000000000000000000
--- a/app/views/projects/merge_requests/_show.html.haml
+++ /dev/null
@@ -1,97 +0,0 @@
-- @content_class = "limit-container-width" unless fluid_layout
-- page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests"
-- page_description @merge_request.description
-- page_card_attributes @merge_request.card_attributes
-- content_for :page_specific_javascripts do
-  = page_specific_javascript_bundle_tag('common_vue')
-  = page_specific_javascript_bundle_tag('diff_notes')
-
-.merge-request{ 'data-url' => merge_request_path(@merge_request, format: :json), 'data-project-path' => project_path(@merge_request.project) }
-  = render "projects/merge_requests/show/mr_title"
-
-  .merge-request-details.issuable-details{ data: { id: @merge_request.project.id } }
-    = render "projects/merge_requests/show/mr_box"
-
-    - if @merge_request.source_branch_exists?
-      = render "projects/merge_requests/show/how_to_merge"
-
-    :javascript
-      window.gl.mrWidgetData = #{serialize_issuable(@merge_request)}
-
-    #js-vue-mr-widget.mr-widget
-
-    - content_for :page_specific_javascripts do
-      = webpack_bundle_tag 'common_vue'
-      = webpack_bundle_tag 'vue_merge_request_widget'
-
-    .content-block.content-block-small.emoji-list-container
-      = render 'award_emoji/awards_block', awardable: @merge_request, inline: true
-
-    .merge-request-tabs-holder{ class: ("js-tabs-affix" unless ENV['RAILS_ENV'] == 'test') }
-      .merge-request-tabs-container
-        .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
-          .fade-left= icon('angle-left')
-          .fade-right= icon('angle-right')
-          .nav-links.scrolling-tabs
-            %ul.merge-request-tabs
-              %li.notes-tab
-                = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#notes', action: 'notes', toggle: 'tab' } do
-                  Discussion
-                  %span.badge= @merge_request.related_notes.user.count
-              - if @merge_request.source_project
-                %li.commits-tab
-                  = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do
-                    Commits
-                    %span.badge= @commits_count
-              - if @pipelines.any?
-                %li.pipelines-tab
-                  = link_to pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do
-                    Pipelines
-                    %span.badge= @pipelines.size
-              %li.diffs-tab
-                = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#diffs', action: 'diffs', toggle: 'tab' } do
-                  Changes
-                  %span.badge= @merge_request.diff_size
-        #resolve-count-app.line-resolve-all-container.prepend-top-10{ "v-cloak" => true }
-          %resolve-count{ "inline-template" => true, ":logged-out" => "#{current_user.nil?}" }
-            %div
-              .line-resolve-all{ "v-show" => "discussionCount > 0",
-                ":class" => "{ 'has-next-btn': !loggedOut && resolvedDiscussionCount !== discussionCount }" }
-                %span.line-resolve-btn.is-disabled{ type: "button",
-                    ":class" => "{ 'is-active': resolvedDiscussionCount === discussionCount }" }
-                  = render "shared/icons/icon_status_success.svg"
-                %span.line-resolve-text
-                  {{ resolvedDiscussionCount }}/{{ discussionCount }} {{ resolvedCountText }} resolved
-              = render "discussions/new_issue_for_all_discussions", merge_request: @merge_request
-              = render "discussions/jump_to_next"
-
-    .tab-content#diff-notes-app
-      #notes.notes.tab-pane.voting_notes
-        .row
-          %section.col-md-12
-            .issuable-discussion
-              = render "projects/merge_requests/discussion"
-
-      #commits.commits.tab-pane
-        -# This tab is always loaded via AJAX
-      #pipelines.pipelines.tab-pane
-        - if @pipelines.any?
-          = render 'projects/commit/pipelines_list', disable_initialization: true, endpoint: pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
-      #diffs.diffs.tab-pane
-        -# This tab is always loaded via AJAX
-
-    .mr-loading-status
-      = spinner
-
-= render 'shared/issuable/sidebar', issuable: @merge_request
-- if @merge_request.can_be_reverted?(current_user)
-  = render "projects/commit/change", type: 'revert', commit: @merge_request.merge_commit, title: @merge_request.title
-- if @merge_request.can_be_cherry_picked?
-  = render "projects/commit/change", type: 'cherry-pick', commit: @merge_request.merge_commit, title: @merge_request.title
-
-:javascript
-  $(function () {
-    window.mergeRequest = new MergeRequest({
-      action: "#{controller.action_name}"
-    });
-  });
diff --git a/app/views/projects/merge_requests/conflicts.html.haml b/app/views/projects/merge_requests/conflicts.html.haml
index 51d59280be826b76ad900ee0ffbcfbcfd8d32660..454bc359b6bea0b3e7be92d6c724f88f0dcd40b3 100644
--- a/app/views/projects/merge_requests/conflicts.html.haml
+++ b/app/views/projects/merge_requests/conflicts.html.haml
@@ -3,15 +3,15 @@
   = page_specific_javascript_bundle_tag('common_vue')
   = page_specific_javascript_bundle_tag('merge_conflicts')
   = page_specific_javascript_tag('lib/ace.js')
-= render "projects/merge_requests/show/mr_title"
+= render "projects/merge_requests/mr_title"
 
 .merge-request-details.issuable-details
-  = render "projects/merge_requests/show/mr_box"
+  = render "projects/merge_requests/mr_box"
 
 = render 'shared/issuable/sidebar', issuable: @merge_request
 
-#conflicts{ "v-cloak" => "true", data: { conflicts_path: conflicts_namespace_project_merge_request_path(@merge_request.project.namespace, @merge_request.project, @merge_request, format: :json),
-    resolve_conflicts_path: resolve_conflicts_namespace_project_merge_request_path(@merge_request.project.namespace, @merge_request.project, @merge_request) } }
+#conflicts{ "v-cloak" => "true", data: { conflicts_path: conflicts_project_merge_request_path(@merge_request.project, @merge_request, format: :json),
+    resolve_conflicts_path: resolve_conflicts_project_merge_request_path(@merge_request.project, @merge_request) } }
   .loading{ "v-if" => "isLoading" }
     %i.fa.fa-spinner.fa-spin
 
diff --git a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml
index 62c9748c5102257b8a6f8eca4c256531a467fb5c..13026b7566ab410fa842bd21e1b5c9126b9ee1f3 100644
--- a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml
+++ b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml
@@ -1,7 +1,7 @@
 .form-horizontal.resolve-conflicts-form
   .form-group
     %label.col-sm-2.control-label{ "for" => "commit-message" }
-      Commit message
+      #{ _('Commit message') }
     .col-sm-10
       .commit-message-container
         .max-width-marker
@@ -13,4 +13,4 @@
           %button.btn.btn-success.js-submit-button{ type: "button", "@click" => "commit()", ":disabled" => "!readyToCommit" }
             %span {{commitButtonText}}
         .col-xs-6.text-right
-          = link_to "Cancel", namespace_project_merge_request_path(@merge_request.project.namespace, @merge_request.project, @merge_request), class: "btn btn-cancel"
+          = link_to "Cancel", project_merge_request_path(@merge_request.project, @merge_request), class: "btn btn-cancel"
diff --git a/app/views/projects/merge_requests/conflicts/show.html.haml b/app/views/projects/merge_requests/conflicts/show.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..454bc359b6bea0b3e7be92d6c724f88f0dcd40b3
--- /dev/null
+++ b/app/views/projects/merge_requests/conflicts/show.html.haml
@@ -0,0 +1,38 @@
+- page_title "Merge Conflicts", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests"
+- content_for :page_specific_javascripts do
+  = page_specific_javascript_bundle_tag('common_vue')
+  = page_specific_javascript_bundle_tag('merge_conflicts')
+  = page_specific_javascript_tag('lib/ace.js')
+= render "projects/merge_requests/mr_title"
+
+.merge-request-details.issuable-details
+  = render "projects/merge_requests/mr_box"
+
+= render 'shared/issuable/sidebar', issuable: @merge_request
+
+#conflicts{ "v-cloak" => "true", data: { conflicts_path: conflicts_project_merge_request_path(@merge_request.project, @merge_request, format: :json),
+    resolve_conflicts_path: resolve_conflicts_project_merge_request_path(@merge_request.project, @merge_request) } }
+  .loading{ "v-if" => "isLoading" }
+    %i.fa.fa-spinner.fa-spin
+
+  .nothing-here-block{ "v-if" => "hasError" }
+    {{conflictsData.errorMessage}}
+
+  = render partial: "projects/merge_requests/conflicts/commit_stats"
+
+  .files-wrapper{ "v-if" => "!isLoading && !hasError" }
+    .files
+      .diff-file.file-holder.conflict{ "v-for" => "file in conflictsData.files" }
+        .js-file-title.file-title
+          %i.fa.fa-fw{ ":class" => "file.iconClass" }
+          %strong {{file.filePath}}
+          = render partial: 'projects/merge_requests/conflicts/file_actions'
+        .diff-content.diff-wrap-lines
+          .diff-wrap-lines.code.file-content.js-syntax-highlight{ "v-show" => "!isParallel && file.resolveMode === 'interactive' && file.type === 'text'" }
+            = render partial: "projects/merge_requests/conflicts/components/inline_conflict_lines"
+          .diff-wrap-lines.code.file-content.js-syntax-highlight{ "v-show" => "isParallel && file.resolveMode === 'interactive' && file.type === 'text'" }
+            %parallel-conflict-lines{ ":file" => "file" }
+          %div{ "v-show" => "file.resolveMode === 'edit' ||  file.type === 'text-editor'" }
+            = render partial: "projects/merge_requests/conflicts/components/diff_file_editor"
+
+    = render partial: "projects/merge_requests/conflicts/submit_form"
diff --git a/app/views/projects/merge_requests/_new_diffs.html.haml b/app/views/projects/merge_requests/creations/_diffs.html.haml
similarity index 100%
rename from app/views/projects/merge_requests/_new_diffs.html.haml
rename to app/views/projects/merge_requests/creations/_diffs.html.haml
diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/creations/_new_compare.html.haml
similarity index 87%
rename from app/views/projects/merge_requests/_new_compare.html.haml
rename to app/views/projects/merge_requests/creations/_new_compare.html.haml
index 0f37abb579c1522d5c252123c16c3a671634987d..4e5aae496b1016c1923351a6b63a03c46816480c 100644
--- a/app/views/projects/merge_requests/_new_compare.html.haml
+++ b/app/views/projects/merge_requests/creations/_new_compare.html.haml
@@ -1,7 +1,7 @@
 %h3.page-title
   New Merge Request
 
-= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], url: new_namespace_project_merge_request_path(@project.namespace, @project), method: :get, html: { class: "merge-request-form form-inline js-requires-input" } do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], url: project_new_merge_request_path(@project), method: :get, html: { class: "merge-request-form form-inline js-requires-input" } do |f|
   .hide.alert.alert-danger.mr-compare-errors
   .merge-request-branches.row
     .col-md-6
@@ -69,7 +69,7 @@
 
 :javascript
   new Compare({
-    targetProjectUrl: "#{update_branches_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}",
-    sourceBranchUrl: "#{branch_from_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}",
-    targetBranchUrl: "#{branch_to_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}"
+    targetProjectUrl: "#{project_new_merge_request_update_branches_path(@source_project)}",
+    sourceBranchUrl: "#{project_new_merge_request_branch_from_path(@source_project)}",
+    targetBranchUrl: "#{project_new_merge_request_branch_to_path(@source_project)}"
   });
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/creations/_new_submit.html.haml
similarity index 75%
rename from app/views/projects/merge_requests/_new_submit.html.haml
rename to app/views/projects/merge_requests/creations/_new_submit.html.haml
index e3ecbee549022ffa8de35179ac7320ba09572eb5..c72dd1d8e296f8ba324b1c9fed2c1d6378d1b2fa 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/creations/_new_submit.html.haml
@@ -31,28 +31,27 @@
           %span.badge= @commits.size
       - if @pipelines.any?
         %li.builds-tab
-          = link_to url_for(params), data: {target: 'div#pipelines', action: 'pipelines', toggle: 'tab'} do
+          = link_to url_for(params.merge(action: 'pipelines')), data: {target: 'div#pipelines', action: 'pipelines', toggle: 'tab'} do
             Pipelines
             %span.badge= @pipelines.size
       %li.diffs-tab
-        = link_to url_for(params.merge(action: 'new_diffs')), data: {target: 'div#diffs', action: 'new/diffs', toggle: 'tab'} do
+        = link_to url_for(params.merge(action: 'diffs')), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do
           Changes
           %span.badge= @merge_request.diff_size
 
     .tab-content
       #commits.commits.tab-pane.active
-        = render "projects/merge_requests/show/commits"
+        = render "projects/merge_requests/commits"
       #diffs.diffs.tab-pane
         -# This tab is always loaded via AJAX
       - if @pipelines.any?
         #pipelines.pipelines.tab-pane
-          = render 'projects/merge_requests/show/pipelines', endpoint: url_for(params.merge(format: :json)), disable_initialization: true
+          = render 'projects/merge_requests/pipelines', endpoint: url_for(params.merge(action: 'pipelines', format: :json)), disable_initialization: true
 
   .mr-loading-status
     = spinner
 
 :javascript
   var merge_request = new MergeRequest({
-    action: "#{(@show_changes_tab ? 'new/diffs' : 'new')}",
-    setUrl: false,
+    action: "#{j params[:tab].presence || 'new'}",
   });
diff --git a/app/views/projects/merge_requests/branch_from.html.haml b/app/views/projects/merge_requests/creations/branch_from.html.haml
similarity index 100%
rename from app/views/projects/merge_requests/branch_from.html.haml
rename to app/views/projects/merge_requests/creations/branch_from.html.haml
diff --git a/app/views/projects/merge_requests/branch_to.html.haml b/app/views/projects/merge_requests/creations/branch_to.html.haml
similarity index 100%
rename from app/views/projects/merge_requests/branch_to.html.haml
rename to app/views/projects/merge_requests/creations/branch_to.html.haml
diff --git a/app/views/projects/merge_requests/new.html.haml b/app/views/projects/merge_requests/creations/new.html.haml
similarity index 100%
rename from app/views/projects/merge_requests/new.html.haml
rename to app/views/projects/merge_requests/creations/new.html.haml
diff --git a/app/views/projects/merge_requests/update_branches.html.haml b/app/views/projects/merge_requests/creations/update_branches.html.haml
similarity index 100%
rename from app/views/projects/merge_requests/update_branches.html.haml
rename to app/views/projects/merge_requests/creations/update_branches.html.haml
diff --git a/app/views/projects/merge_requests/diffs.html.haml b/app/views/projects/merge_requests/diffs.html.haml
deleted file mode 100644
index 2a5b8b1441ef89345d4d5357b4f5b557d80fe7e7..0000000000000000000000000000000000000000
--- a/app/views/projects/merge_requests/diffs.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-= render "show"
diff --git a/app/views/projects/merge_requests/show/_diffs.html.haml b/app/views/projects/merge_requests/diffs/_diffs.html.haml
similarity index 84%
rename from app/views/projects/merge_requests/show/_diffs.html.haml
rename to app/views/projects/merge_requests/diffs/_diffs.html.haml
index 7f0913ea5160f6ca56ec65975cd224118637eb9f..fb31e2fef004b243cd4975a7542c2e63735d1333 100644
--- a/app/views/projects/merge_requests/show/_diffs.html.haml
+++ b/app/views/projects/merge_requests/diffs/_diffs.html.haml
@@ -1,5 +1,5 @@
 - if @merge_request_diff.collected? || @merge_request_diff.overflow?
-  = render 'projects/merge_requests/show/versions'
+  = render 'projects/merge_requests/diffs/versions'
   = render "projects/diffs/diffs", diffs: @diffs, environment: @environment
 - elsif @merge_request_diff.empty?
   .nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch}
diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/diffs/_versions.html.haml
similarity index 93%
rename from app/views/projects/merge_requests/show/_versions.html.haml
rename to app/views/projects/merge_requests/diffs/_versions.html.haml
index 0999b95c9c9d34f33b484a1ed0494f1daea5bb49..9f7152b9824eeb6b181108be72049a60f4d0a2d7 100644
--- a/app/views/projects/merge_requests/show/_versions.html.haml
+++ b/app/views/projects/merge_requests/diffs/_versions.html.haml
@@ -77,7 +77,7 @@
         = icon('info-circle')
         Selected versions have different base commits.
         Changes will include
-        = link_to namespace_project_compare_path(@project.namespace, @project, from: @start_version.base_commit_sha, to: @merge_request_diff.base_commit_sha) do
+        = link_to project_compare_path(@project, from: @start_version.base_commit_sha, to: @merge_request_diff.base_commit_sha) do
           new commits
         from
         = succeed '.' do
@@ -94,4 +94,4 @@
         of the diff.
 
         .pull-right
-          = link_to 'Show latest version', diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn btn-sm'
+          = link_to 'Show latest version', diffs_project_merge_request_path(@project, @merge_request), class: 'btn btn-sm'
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index 6d75a9f34a3d3fe8bea155fe8b9c6cdccb424bb3..bfeb746ee83cfbf0679b5799f374b253e1161ad2 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -1,5 +1,7 @@
 - @no_container = true
 - @can_bulk_update = can?(current_user, :admin_merge_request, @project)
+- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
+- new_merge_request_path = project_new_merge_request_path(merge_project) if merge_project
 
 - page_title "Merge Requests"
 - unless @project.default_issues_tracker?
@@ -10,6 +12,9 @@
   = webpack_bundle_tag 'common_vue'
   = webpack_bundle_tag 'filtered_search'
 
+- if show_new_nav?
+  - content_for :breadcrumbs_extra do
+    = render "projects/merge_requests/nav_btns", merge_project: merge_project, new_merge_request_path: new_merge_request_path
 
 = render 'projects/last_push'
 
@@ -17,13 +22,8 @@
   %div{ class: container_class }
     .top-area
       = render 'shared/issuable/nav', type: :merge_requests
-      .nav-controls
-        - if @can_bulk_update
-          = button_tag "Edit Merge Requests", class: "btn js-bulk-update-toggle"
-        - merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
-        - if merge_project
-          = link_to new_namespace_project_merge_request_path(merge_project.namespace, merge_project), class: "btn btn-new", title: "New merge request" do
-            New merge request
+      .nav-controls{ class: ("visible-xs" if show_new_nav?) }
+        = render "projects/merge_requests/nav_btns", merge_project: merge_project, new_merge_request_path: new_merge_request_path
 
     = render 'shared/issuable/search_bar', type: :merge_requests
 
@@ -33,4 +33,4 @@
     .merge-requests-holder
       = render 'merge_requests'
 - else
-  = render 'shared/empty_states/merge_requests', button_path: new_namespace_project_merge_request_path(@project.namespace, @project)
+  = render 'shared/empty_states/merge_requests', button_path: new_merge_request_path
diff --git a/app/views/projects/merge_requests/invalid.html.haml b/app/views/projects/merge_requests/invalid.html.haml
index a00d3128ffef70c8d74e3f77f523e2ac5b81b303..6df19d6438bf53fcc9f314dfe9f5d2690469ba7d 100644
--- a/app/views/projects/merge_requests/invalid.html.haml
+++ b/app/views/projects/merge_requests/invalid.html.haml
@@ -1,8 +1,8 @@
 - page_title "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests"
 
 .merge-request
-  = render "projects/merge_requests/show/mr_title"
-  = render "projects/merge_requests/show/mr_box"
+  = render "projects/merge_requests/mr_title"
+  = render "projects/merge_requests/mr_box"
 
   .alert.alert-danger
     %p
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index 2a5b8b1441ef89345d4d5357b4f5b557d80fe7e7..13012151349f7de77e6a4846800b577df84bf189 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -1 +1,97 @@
-= render "show"
+- @content_class = "limit-container-width" unless fluid_layout
+- page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests"
+- page_description @merge_request.description
+- page_card_attributes @merge_request.card_attributes
+- content_for :page_specific_javascripts do
+  = page_specific_javascript_bundle_tag('common_vue')
+  = page_specific_javascript_bundle_tag('diff_notes')
+
+.merge-request{ 'data-url' => merge_request_path(@merge_request, format: :json), 'data-project-path' => project_path(@merge_request.project) }
+  = render "projects/merge_requests/mr_title"
+
+  .merge-request-details.issuable-details{ data: { id: @merge_request.project.id } }
+    = render "projects/merge_requests/mr_box"
+
+    - if @merge_request.source_branch_exists?
+      = render "projects/merge_requests/how_to_merge"
+
+    :javascript
+      window.gl.mrWidgetData = #{serialize_issuable(@merge_request)}
+
+    #js-vue-mr-widget.mr-widget
+
+    - content_for :page_specific_javascripts do
+      = webpack_bundle_tag 'common_vue'
+      = webpack_bundle_tag 'vue_merge_request_widget'
+
+    .content-block.content-block-small.emoji-list-container
+      = render 'award_emoji/awards_block', awardable: @merge_request, inline: true
+
+    .merge-request-tabs-holder{ class: ("js-tabs-affix" unless ENV['RAILS_ENV'] == 'test') }
+      .merge-request-tabs-container
+        .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
+          .fade-left= icon('angle-left')
+          .fade-right= icon('angle-right')
+          .nav-links.scrolling-tabs
+            %ul.merge-request-tabs
+              %li.notes-tab
+                = link_to project_merge_request_path(@project, @merge_request), data: { target: 'div#notes', action: 'show', toggle: 'tab' } do
+                  Discussion
+                  %span.badge= @merge_request.related_notes.user.count
+              - if @merge_request.source_project
+                %li.commits-tab
+                  = link_to commits_project_merge_request_path(@project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do
+                    Commits
+                    %span.badge= @commits_count
+              - if @pipelines.any?
+                %li.pipelines-tab
+                  = link_to pipelines_project_merge_request_path(@project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do
+                    Pipelines
+                    %span.badge= @pipelines.size
+              %li.diffs-tab
+                = link_to diffs_project_merge_request_path(@project, @merge_request), data: { target: 'div#diffs', action: 'diffs', toggle: 'tab' } do
+                  Changes
+                  %span.badge= @merge_request.diff_size
+        #resolve-count-app.line-resolve-all-container.prepend-top-10{ "v-cloak" => true }
+          %resolve-count{ "inline-template" => true, ":logged-out" => "#{current_user.nil?}" }
+            %div
+              .line-resolve-all{ "v-show" => "discussionCount > 0",
+                ":class" => "{ 'has-next-btn': !loggedOut && resolvedDiscussionCount !== discussionCount }" }
+                %span.line-resolve-btn.is-disabled{ type: "button",
+                    ":class" => "{ 'is-active': resolvedDiscussionCount === discussionCount }" }
+                  = render "shared/icons/icon_status_success.svg"
+                %span.line-resolve-text
+                  {{ resolvedDiscussionCount }}/{{ discussionCount }} {{ resolvedCountText }} resolved
+              = render "discussions/new_issue_for_all_discussions", merge_request: @merge_request
+              = render "discussions/jump_to_next"
+
+    .tab-content#diff-notes-app
+      #notes.notes.tab-pane.voting_notes
+        .row
+          %section.col-md-12
+            .issuable-discussion
+              = render "projects/merge_requests/discussion"
+
+      #commits.commits.tab-pane
+        -# This tab is always loaded via AJAX
+      #pipelines.pipelines.tab-pane
+        - if @pipelines.any?
+          = render 'projects/commit/pipelines_list', disable_initialization: true, endpoint: pipelines_project_merge_request_path(@project, @merge_request)
+      #diffs.diffs.tab-pane
+        -# This tab is always loaded via AJAX
+
+    .mr-loading-status
+      = spinner
+
+= render 'shared/issuable/sidebar', issuable: @merge_request
+- if @merge_request.can_be_reverted?(current_user)
+  = render "projects/commit/change", type: 'revert', commit: @merge_request.merge_commit, title: @merge_request.title
+- if @merge_request.can_be_cherry_picked?
+  = render "projects/commit/change", type: 'cherry-pick', commit: @merge_request.merge_commit, title: @merge_request.title
+
+:javascript
+  $(function () {
+    window.mergeRequest = new MergeRequest({
+      action: "#{j params[:tab].presence || 'show'}",
+    });
+  });
diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml
index 9a95b2a82ffd710564998e8516d3258076d17c80..2e74b1b83cb280b264aa2cbe595604aeaa9da72e 100644
--- a/app/views/projects/milestones/_form.html.haml
+++ b/app/views/projects/milestones/_form.html.haml
@@ -19,7 +19,7 @@
   .form-actions
     - if @milestone.new_record?
       = f.submit 'Create milestone', class: "btn-create btn"
-      = link_to "Cancel", namespace_project_milestones_path(@project.namespace, @project), class: "btn btn-cancel"
+      = link_to "Cancel", project_milestones_path(@project), class: "btn btn-cancel"
     - else
       = f.submit 'Save changes', class: "btn-save btn"
-      = link_to "Cancel", namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-cancel"
+      = link_to "Cancel", project_milestone_path(@project, @milestone), class: "btn btn-cancel"
diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml
index 77b566db6b6003fa475edce90af2fb6d0d4fe88b..bc82b45f9023dfe0f7129e1f278dbf19d607031f 100644
--- a/app/views/projects/milestones/_milestone.html.haml
+++ b/app/views/projects/milestones/_milestone.html.haml
@@ -1,5 +1,5 @@
 = render 'shared/milestones/milestone',
-          milestone_path: namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone),
-          issues_path: namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title),
-          merge_requests_path: namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title),
+          milestone_path: project_milestone_path(milestone.project, milestone),
+          issues_path: project_issues_path(milestone.project, milestone_title: milestone.title),
+          merge_requests_path: project_merge_requests_path(milestone.project, milestone_title: milestone.title),
           milestone: milestone
diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml
index e1096bd1d67f5e6af2b74fa8f6e8dbe5ca454c1c..e53fcd6e4250325db499442b391b6c0758946afd 100644
--- a/app/views/projects/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -9,7 +9,7 @@
     .nav-controls
       = render 'shared/milestones_sort_dropdown'
       - if can?(current_user, :admin_milestone, @project)
-        = link_to new_namespace_project_milestone_path(@project.namespace, @project), class: 'btn btn-new', title: 'New milestone' do
+        = link_to new_project_milestone_path(@project), class: 'btn btn-new', title: 'New milestone' do
           New milestone
 
   .milestones
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 4b692aba11cf9eec00508a80e8d38a338b040efd..0bf0e11c107dbbfbed4b112a4fe33d87c1569831 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -23,14 +23,14 @@
     .milestone-buttons
       - if can?(current_user, :admin_milestone, @project)
         - if @milestone.active?
-          = link_to 'Close milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-nr btn-grouped"
+          = link_to 'Close milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-nr btn-grouped"
         - else
-          = link_to 'Reopen milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-nr btn-grouped"
+          = link_to 'Reopen milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-nr btn-grouped"
 
-        = link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped btn-nr" do
+        = link_to edit_project_milestone_path(@project, @milestone), class: "btn btn-grouped btn-nr" do
           Edit
 
-        = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-danger" do
+        = link_to project_milestone_path(@project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-danger" do
           Delete
 
       %a.btn.btn-default.btn-grouped.pull-right.visible-xs-block.js-sidebar-toggle{ href: "#" }
diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml
index ed6077f6c6bf208da7cadf20fe6cccc82e7a3d5f..e8c26636be9d4fad76ae0b59bc614b6ac2264b85 100644
--- a/app/views/projects/network/show.html.haml
+++ b/app/views/projects/network/show.html.haml
@@ -6,7 +6,7 @@
 %div{ class: container_class }
   .project-network
     .controls
-      = form_tag namespace_project_network_path(@project.namespace, @project, @id), method: :get, class: 'form-inline network-form' do |f|
+      = form_tag project_network_path(@project, @id), method: :get, class: 'form-inline network-form' do |f|
         = text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: "Git revision", class: 'search-input form-control input-mx-250 search-sha'
         = button_tag class: 'btn btn-success' do
           = icon('search')
diff --git a/app/views/projects/no_repo.html.haml b/app/views/projects/no_repo.html.haml
index 1cf286ddc40ce18ed4f762a19e5ff933b5c3aeca..ba5845877e54fc25ee6b38bcda47dfeef1fcf055 100644
--- a/app/views/projects/no_repo.html.haml
+++ b/app/views/projects/no_repo.html.haml
@@ -9,12 +9,12 @@
 %hr
 
 .no-repo-actions
-  = link_to namespace_project_repository_path(@project.namespace, @project), method: :post, class: 'btn btn-primary' do
+  = link_to project_repository_path(@project), method: :post, class: 'btn btn-primary' do
     #{ _('Create empty bare repository') }
 
   %strong.prepend-left-10.append-right-10 or
 
-  = link_to new_namespace_project_import_path(@project.namespace, @project), class: 'btn' do
+  = link_to new_project_import_path(@project), class: 'btn' do
     #{ _('Import repository') }
 
 - if can? current_user, :remove_project, @project
diff --git a/app/views/projects/notes/_more_actions_dropdown.html.haml b/app/views/projects/notes/_more_actions_dropdown.html.haml
index e0d45054854c905e98060ff4d1167258eed05433..75a4687e1e3ddeb10a192fdc34298bc0f5b42c3f 100644
--- a/app/views/projects/notes/_more_actions_dropdown.html.haml
+++ b/app/views/projects/notes/_more_actions_dropdown.html.haml
@@ -1,14 +1,19 @@
-.dropdown.more-actions
-  = button_tag title: 'More actions', class: 'note-action-button more-actions-toggle has-tooltip btn btn-transparent', data: { toggle: 'dropdown', container: 'body' } do
-    = icon('ellipsis-v', class: 'icon')
-  %ul.dropdown-menu.more-actions-dropdown.dropdown-open-left
-    %li
-      = button_tag 'Edit comment', class: 'js-note-edit btn btn-transparent'
-    %li.divider
-      %li
-        = link_to new_abuse_report_path(user_id: note.author.id, ref_url: noteable_note_url(note)) do
-          Report as abuse
-    - if note_editable
-      %li
-        = link_to note_url(note), method: :delete, data: { confirm: 'Are you sure you want to delete this comment?' }, remote: true, class: 'js-note-delete' do
-          %span.text-danger Delete comment
+- is_current_user = current_user == note.author
+
+- if note_editable || !is_current_user
+  .dropdown.more-actions
+    = button_tag title: 'More actions', class: 'note-action-button more-actions-toggle has-tooltip btn btn-transparent', data: { toggle: 'dropdown', container: 'body' } do
+      = icon('ellipsis-v', class: 'icon')
+    %ul.dropdown-menu.more-actions-dropdown.dropdown-open-left
+      - if note_editable
+        %li
+          = button_tag 'Edit comment', class: 'js-note-edit btn btn-transparent'
+        %li.divider
+      - unless is_current_user
+        %li
+          = link_to new_abuse_report_path(user_id: note.author.id, ref_url: noteable_note_url(note)) do
+            Report as abuse
+      - if note_editable
+        %li
+          = link_to note_url(note), method: :delete, data: { confirm: 'Are you sure you want to delete this comment?' }, remote: true, class: 'js-note-delete' do
+            %span.text-danger Delete comment
diff --git a/app/views/projects/pages/_destroy.haml b/app/views/projects/pages/_destroy.haml
index 42d9ef5ccba3f1f87de3739fbef2535c46ae8f53..7d6c30b7f8d29d5d0ff5bb20f9ef5eb7520c91f2 100644
--- a/app/views/projects/pages/_destroy.haml
+++ b/app/views/projects/pages/_destroy.haml
@@ -7,6 +7,6 @@
         %p
           Removing the pages will prevent from exposing them to outside world.
         .form-actions
-          = link_to 'Remove pages', namespace_project_pages_path(@project.namespace, @project), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove"
+          = link_to 'Remove pages', project_pages_path(@project), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove"
   - else
     .nothing-here-block Only the project owner can remove pages
diff --git a/app/views/projects/pages/_list.html.haml b/app/views/projects/pages/_list.html.haml
index 4f2dd1a13983853109d36ae5ed24120561f2de3c..a85cda407afb1b8580ec58f3a5d524b2533c7524 100644
--- a/app/views/projects/pages/_list.html.haml
+++ b/app/views/projects/pages/_list.html.haml
@@ -6,8 +6,8 @@
       - @domains.each do |domain|
         %li
           .pull-right
-            = link_to 'Details', namespace_project_pages_domain_path(@project.namespace, @project, domain), class: "btn btn-sm btn-grouped"
-            = link_to 'Remove', namespace_project_pages_domain_path(@project.namespace, @project, domain), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped"
+            = link_to 'Details', project_pages_domain_path(@project, domain), class: "btn btn-sm btn-grouped"
+            = link_to 'Remove', project_pages_domain_path(@project, domain), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped"
           .clearfix
             %span= link_to domain.domain, domain.url
           %p
diff --git a/app/views/projects/pages/show.html.haml b/app/views/projects/pages/show.html.haml
index b22a54d75c803c8e50303a81967e7c6f5dad79bc..098b0ef56efb656ad31a041d7bd7ff5cd2686ff8 100644
--- a/app/views/projects/pages/show.html.haml
+++ b/app/views/projects/pages/show.html.haml
@@ -5,7 +5,7 @@
   Pages
 
   - if can?(current_user, :update_pages, @project) && (Gitlab.config.pages.external_http || Gitlab.config.pages.external_https)
-    = link_to new_namespace_project_pages_domain_path(@project.namespace, @project), class: 'btn btn-new pull-right', title: 'New Domain' do
+    = link_to new_project_pages_domain_path(@project), class: 'btn btn-new pull-right', title: 'New Domain' do
       %i.fa.fa-plus
       New Domain
 
diff --git a/app/views/projects/pipeline_schedules/_form.html.haml b/app/views/projects/pipeline_schedules/_form.html.haml
index e8dedf2620645e36ff7e8057aa94589a693e6e70..fc7fa5c1876792b560e968dc7dd8415766557476 100644
--- a/app/views/projects/pipeline_schedules/_form.html.haml
+++ b/app/views/projects/pipeline_schedules/_form.html.haml
@@ -7,7 +7,7 @@
   .form-group
     .col-md-9
       = f.label :description, _('Description'), class: 'label-light'
-      = f.text_field :description, class: 'form-control', required: true, autofocus: true, placeholder: _('PipelineSchedules|Provide a short description for this pipeline')
+      = f.text_field :description, class: 'form-control', required: true, autofocus: true, placeholder: s_('PipelineSchedules|Provide a short description for this pipeline')
   .form-group
     .col-md-9
       = f.label :cron, _('Interval Pattern'), class: 'label-light'
@@ -15,19 +15,19 @@
   .form-group
     .col-md-9
       = f.label :cron_timezone, _('Cron Timezone'), class: 'label-light'
-      = dropdown_tag(_("Select a timezone"), options: { toggle_class: 'btn js-timezone-dropdown', title: _("Select a timezone"), filter: true, placeholder: _("Filter"), data: { data: timezone_data } } )
+      = dropdown_tag(_("Select a timezone"), options: { toggle_class: 'btn js-timezone-dropdown', title: _("Select a timezone"), filter: true, placeholder: s_("OfSearchInADropdown|Filter"), data: { data: timezone_data } } )
       = f.text_field :cron_timezone, value: @schedule.cron_timezone, id: 'schedule_cron_timezone', class: 'hidden', name: 'schedule[cron_timezone]', required: true
   .form-group
     .col-md-9
       = f.label :ref, _('Target Branch'), class: 'label-light'
-      = dropdown_tag(_("Select target branch"), options: { toggle_class: 'btn js-target-branch-dropdown', dropdown_class: 'git-revision-dropdown', title: _("Select target branch"), filter: true, placeholder: _("Filter"), data: { data: @project.repository.branch_names, default_branch: @project.default_branch } } )
+      = dropdown_tag(_("Select target branch"), options: { toggle_class: 'btn js-target-branch-dropdown', dropdown_class: 'git-revision-dropdown', title: _("Select target branch"), filter: true, placeholder: s_("OfSearchInADropdown|Filter"), data: { data: @project.repository.branch_names, default_branch: @project.default_branch } } )
       = f.text_field :ref, value: @schedule.ref, id: 'schedule_ref', class: 'hidden', name: 'schedule[ref]', required: true
   .form-group
     .col-md-9
-      = f.label  :active, _('PipelineSchedules|Activated'), class: 'label-light'
+      = f.label  :active, s_('PipelineSchedules|Activated'), class: 'label-light'
       %div
         = f.check_box :active, required: false, value: @schedule.active?
-        Active
+        = _('Active')
   .footer-block.row-content-block
     = f.submit _('Save pipeline schedule'), class: 'btn btn-create', tabindex: 3
     = link_to _('Cancel'), pipeline_schedules_path(@project), class: 'btn btn-cancel'
diff --git a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
index 2d3344a4aaf349872bdac0bd687fdf0d838319cd..08ccd57c81a7453cdd51d312831cc9feff1a85d3 100644
--- a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
+++ b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
@@ -9,16 +9,16 @@
     %td
       - if pipeline_schedule.last_pipeline
         .status-icon-container{ class: "ci-status-icon-#{pipeline_schedule.last_pipeline.status}" }
-          = link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline_schedule.last_pipeline.id) do
+          = link_to project_pipeline_path(@project, pipeline_schedule.last_pipeline.id) do
             = ci_icon_for_status(pipeline_schedule.last_pipeline.status)
             %span ##{pipeline_schedule.last_pipeline.id}
       - else
-        = _("PipelineSchedules|None")
+        = s_("PipelineSchedules|None")
     %td.next-run-cell
       - if pipeline_schedule.active?
         = time_ago_with_tooltip(pipeline_schedule.real_next_run)
       - else
-        = _("PipelineSchedules|Inactive")
+        = s_("PipelineSchedules|Inactive")
     %td
       - if pipeline_schedule.owner
         = image_tag avatar_icon(pipeline_schedule.owner, 20), class: "avatar s20"
diff --git a/app/views/projects/pipeline_schedules/index.html.haml b/app/views/projects/pipeline_schedules/index.html.haml
index 4a96ee652d2f790cde0cc823c229870870904bd9..05fe80e5fed28bb56fb48469b208cb2eb8e7c494 100644
--- a/app/views/projects/pipeline_schedules/index.html.haml
+++ b/app/views/projects/pipeline_schedules/index.html.haml
@@ -13,8 +13,8 @@
     = render "tabs", schedule_path_proc: schedule_path_proc, all_schedules: @all_schedules, scope: @scope
 
     .nav-controls
-      = link_to new_namespace_project_pipeline_schedule_path(@project.namespace, @project), class: 'btn btn-create' do
-        %span New schedule
+      = link_to new_project_pipeline_schedule_path(@project), class: 'btn btn-create' do
+        %span= _('New schedule')
 
   - if @schedules.present?
     %ul.content-list
diff --git a/app/views/projects/pipelines/_head.html.haml b/app/views/projects/pipelines/_head.html.haml
index d2f0cb0806f88980bf388ee454ebd6ab16f497f9..ee2f236cec4a923010e118802565c3156a9aa563 100644
--- a/app/views/projects/pipelines/_head.html.haml
+++ b/app/views/projects/pipelines/_head.html.haml
@@ -29,6 +29,6 @@
 
         - if @project.feature_available?(:builds, current_user) && !@project.empty_repo?
           = nav_link(path: 'pipelines#charts') do
-            = link_to charts_namespace_project_pipelines_path(@project.namespace, @project), title: 'Charts', class: 'shortcuts-pipelines-charts' do
+            = link_to charts_project_pipelines_path(@project), title: 'Charts', class: 'shortcuts-pipelines-charts' do
               %span
                 Charts
diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml
index 673c3370b62c6c03e7fee2b865087a4cb300f465..f514930673464cf20a7328ee6f3faf7a035fe2cf 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -26,10 +26,10 @@
   .well-segment.branch-info
     .icon-container.commit-icon
       = custom_icon("icon_commit")
-    = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @pipeline.sha), class: "commit-sha js-details-short"
+    = link_to @commit.short_id, project_commit_path(@project, @pipeline.sha), class: "commit-sha js-details-short"
     = link_to("#", class: "js-details-expand hidden-xs hidden-sm") do
       %span.text-expander
         \...
     %span.js-details-content.hide
-      = link_to @pipeline.sha, namespace_project_commit_path(@project.namespace, @project, @pipeline.sha), class: "commit-sha commit-hash-full"
+      = link_to @pipeline.sha, project_commit_path(@project, @pipeline.sha), class: "commit-sha commit-hash-full"
     = clipboard_button(text: @pipeline.sha, title: "Copy commit SHA to clipboard")
diff --git a/app/views/projects/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml
index 85550e8fd3200e4be90c7f7526160089c6aebed7..ad61f033a1c390fca67fece889c8ab070a941864 100644
--- a/app/views/projects/pipelines/_with_tabs.html.haml
+++ b/app/views/projects/pipelines/_with_tabs.html.haml
@@ -3,15 +3,15 @@
 .tabs-holder
   %ul.pipelines-tabs.nav-links.no-top.no-bottom
     %li.js-pipeline-tab-link
-      = link_to namespace_project_pipeline_path(@project.namespace, @project, @pipeline), data: { target: 'div#js-tab-pipeline', action: 'pipelines', toggle: 'tab' },  class: 'pipeline-tab' do
+      = link_to project_pipeline_path(@project, @pipeline), data: { target: 'div#js-tab-pipeline', action: 'pipelines', toggle: 'tab' },  class: 'pipeline-tab' do
         Pipeline
     %li.js-builds-tab-link
-      = link_to builds_namespace_project_pipeline_path(@project.namespace, @project, @pipeline), data: {target: 'div#js-tab-builds', action: 'builds', toggle: 'tab' }, class: 'builds-tab' do
+      = link_to builds_project_pipeline_path(@project, @pipeline), data: {target: 'div#js-tab-builds', action: 'builds', toggle: 'tab' }, class: 'builds-tab' do
         Jobs
         %span.badge.js-builds-counter= pipeline.statuses.count
     - if failed_builds.present?
       %li.js-failures-tab-link
-        = link_to failures_namespace_project_pipeline_path(@project.namespace, @project, @pipeline), data: {target: 'div#js-tab-failures', action: 'failures', toggle: 'tab' }, class: 'failures-tab' do
+        = link_to failures_project_pipeline_path(@project, @pipeline), data: {target: 'div#js-tab-failures', action: 'failures', toggle: 'tab' }, class: 'failures-tab' do
           Failed Jobs
           %span.badge.js-failures-counter= failed_builds.count
 
diff --git a/app/views/projects/pipelines/charts.html.haml b/app/views/projects/pipelines/charts.html.haml
index 4a5043aac3c38b2030abce3e018df9deb8f2bd8a..78002e8cd64cd0e11f359a6da2fb48be3a906d51 100644
--- a/app/views/projects/pipelines/charts.html.haml
+++ b/app/views/projects/pipelines/charts.html.haml
@@ -1,5 +1,5 @@
 - @no_container = true
-- page_title "Charts", "Pipelines"
+- page_title _("Charts"), _("Pipelines")
 - content_for :page_specific_javascripts do
   = page_specific_javascript_bundle_tag('common_d3')
   = page_specific_javascript_bundle_tag('graphs')
@@ -8,14 +8,14 @@
 %div{ class: container_class }
   .sub-header-block
     .oneline
-      A collection of graphs for Continuous Integration
+      = _("A collection of graphs regarding Continuous Integration")
 
   #charts.ci-charts
     .row
       .col-md-6
         = render 'projects/pipelines/charts/overall'
       .col-md-6
-        = render 'projects/pipelines/charts/build_times'
+        = render 'projects/pipelines/charts/pipeline_times'
 
     %hr
-    = render 'projects/pipelines/charts/builds'
+    = render 'projects/pipelines/charts/pipelines'
diff --git a/app/views/projects/pipelines/charts/_overall.haml b/app/views/projects/pipelines/charts/_overall.haml
index 0b7e3d22dd73de3d46a7eaa6a119bec65ce3e40f..66786c7ff596675f66388066404f5c19c692da3e 100644
--- a/app/views/projects/pipelines/charts/_overall.haml
+++ b/app/views/projects/pipelines/charts/_overall.haml
@@ -1,19 +1,15 @@
-%h4 Overall stats
+%h4= s_("PipelineCharts|Overall statistics")
 %ul
   %li
-    Total:
-    %strong= pluralize @project.builds.count(:all), 'job'
+    = s_("PipelineCharts|Total:")
+    %strong= n_("1 pipeline", "%d pipelines", @counts[:total]) % @counts[:total]
   %li
-    Successful:
-    %strong= pluralize @project.builds.success.count(:all), 'job'
+    = s_("PipelineCharts|Successful:")
+    %strong= n_("1 pipeline", "%d pipelines", @counts[:success]) % @counts[:success]
   %li
-    Failed:
-    %strong= pluralize @project.builds.failed.count(:all), 'job'
+    = s_("PipelineCharts|Failed:")
+    %strong= n_("1 pipeline", "%d pipelines", @counts[:failed]) % @counts[:failed]
   %li
-    Success ratio:
+    = s_("PipelineCharts|Success ratio:")
     %strong
-      #{success_ratio(@project.builds.success, @project.builds.failed)}%
-  %li
-    Commits covered:
-    %strong
-      = @project.pipelines.count(:all)
+      #{success_ratio(@counts)}%
diff --git a/app/views/projects/pipelines/charts/_build_times.haml b/app/views/projects/pipelines/charts/_pipeline_times.haml
similarity index 77%
rename from app/views/projects/pipelines/charts/_build_times.haml
rename to app/views/projects/pipelines/charts/_pipeline_times.haml
index bb0975a953596887beb0e72478500c380448688a..1292f580a8171843aa3462cd9fbe7a44d5f0c592 100644
--- a/app/views/projects/pipelines/charts/_build_times.haml
+++ b/app/views/projects/pipelines/charts/_pipeline_times.haml
@@ -1,12 +1,12 @@
 %div
   %p.light
-    Commit duration in minutes for last 30 commits
+    = _("Commit duration in minutes for last 30 commits")
 
   %canvas#build_timesChart{ height: 200 }
 
 :javascript
   var data = {
-    labels : #{@charts[:build_times].labels.to_json},
+    labels : #{@charts[:pipeline_times].labels.to_json},
     datasets : [
       {
         fillColor : "rgba(220,220,220,0.5)",
@@ -14,7 +14,7 @@
         barStrokeWidth: 1,
         barValueSpacing: 1,
         barDatasetSpacing: 1,
-        data : #{@charts[:build_times].build_times.to_json}
+        data : #{@charts[:pipeline_times].pipeline_times.to_json}
       }
     ]
   }
diff --git a/app/views/projects/pipelines/charts/_builds.haml b/app/views/projects/pipelines/charts/_pipelines.haml
similarity index 88%
rename from app/views/projects/pipelines/charts/_builds.haml
rename to app/views/projects/pipelines/charts/_pipelines.haml
index b6f453b97363776b955e2c775ce7275781e02a26..be884448087397605b883e0cdb8d68205cfd10ab 100644
--- a/app/views/projects/pipelines/charts/_builds.haml
+++ b/app/views/projects/pipelines/charts/_pipelines.haml
@@ -1,29 +1,29 @@
-%h4 Pipelines charts
+%h4= _("Pipelines charts")
 %p
   &nbsp;
   %span.cgreen
     = icon("circle")
-    success
+    = s_("Pipeline|success")
   &nbsp;
   %span.cgray
     = icon("circle")
-    all
+    = s_("Pipeline|all")
 
 .prepend-top-default
   %p.light
-    Jobs 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
-    Jobs 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
-    Jobs for last year
+    = _("Jobs for last year")
   %canvas#yearChart.padded{ height: 250 }
 
 - [:week, :month, :year].each do |scope|
diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml
index 38237d2d97d4f1f874b0652cfcab20d774178f20..c1729850cf4ec01f71404289b2228df12b9c64b9 100644
--- a/app/views/projects/pipelines/index.html.haml
+++ b/app/views/projects/pipelines/index.html.haml
@@ -2,10 +2,10 @@
 - page_title "Pipelines"
 = render "projects/pipelines/head"
 
-#pipelines-list-vue{ data: { endpoint: namespace_project_pipelines_path(@project.namespace, @project, format: :json),
+#pipelines-list-vue{ data: { endpoint: project_pipelines_path(@project, format: :json),
   "css-class" => container_class,
   "help-page-path" => help_page_path('ci/quick_start/README'),
-  "new-pipeline-path" => new_namespace_project_pipeline_path(@project.namespace, @project),
+  "new-pipeline-path" => new_project_pipeline_path(@project),
   "can-create-pipeline" => can?(current_user, :create_pipeline, @project).to_s,
   "all-path" =>  project_pipelines_path(@project),
   "pending-path" => project_pipelines_path(@project, scope: :pending),
diff --git a/app/views/projects/pipelines/new.html.haml b/app/views/projects/pipelines/new.html.haml
index 71a8e490c3e5bea9b075d062bb36daa1193094d3..308f2611e024ec38dd2d048118dfb4aba9d3c8eb 100644
--- a/app/views/projects/pipelines/new.html.haml
+++ b/app/views/projects/pipelines/new.html.haml
@@ -4,7 +4,7 @@
   New Pipeline
 %hr
 
-= form_for @pipeline, as: :pipeline, url: namespace_project_pipelines_path(@project.namespace, @project), html: { id: "new-pipeline-form", class: "form-horizontal js-new-pipeline-form js-requires-input" } do |f|
+= form_for @pipeline, as: :pipeline, url: project_pipelines_path(@project), html: { id: "new-pipeline-form", class: "form-horizontal js-new-pipeline-form js-requires-input" } do |f|
   = form_errors(@pipeline)
   .form-group
     = f.label :ref, 'Create for', class: 'control-label'
@@ -17,7 +17,7 @@
       .help-block Existing branch name, tag
   .form-actions
     = f.submit 'Create pipeline', class: 'btn btn-create', tabindex: 3
-    = link_to 'Cancel', namespace_project_pipelines_path(@project.namespace, @project), class: 'btn btn-cancel'
+    = link_to 'Cancel', project_pipelines_path(@project), class: 'btn btn-cancel'
 
 :javascript
   var availableRefs = #{@project.repository.ref_names.to_json};
diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml
index b39453a50fbcb7e099817b557642b0cab99d9d15..63f85fc69a2c5ebd11f5ef4e83f9fbc1ae19b7b7 100644
--- a/app/views/projects/pipelines/show.html.haml
+++ b/app/views/projects/pipelines/show.html.haml
@@ -8,7 +8,7 @@
 
   = render "projects/pipelines/with_tabs", pipeline: @pipeline
 
-.js-pipeline-details-vue{ data: { endpoint: namespace_project_pipeline_path(@project.namespace, @project, @pipeline, format: :json) } }
+.js-pipeline-details-vue{ data: { endpoint: project_pipeline_path(@project, @pipeline, format: :json) } }
 
 - content_for :page_specific_javascripts do
   = webpack_bundle_tag('common_vue')
diff --git a/app/views/projects/pipelines_settings/_badge.html.haml b/app/views/projects/pipelines_settings/_badge.html.haml
index 43bbd735059929fb06dc3417de5fb2b5a9631ef8..3de518c8b9aab2395d342b6e470499683087ab15 100644
--- a/app/views/projects/pipelines_settings/_badge.html.haml
+++ b/app/views/projects/pipelines_settings/_badge.html.haml
@@ -1,8 +1,8 @@
 %div{ class: badge.title.gsub(' ', '-') }
-  .col-lg-3.profile-settings-sidebar
+  .col-lg-4.profile-settings-sidebar
     %h4.prepend-top-0
       = badge.title.capitalize
-  .col-lg-9
+  .col-lg-8
     .prepend-top-10
       .panel.panel-default
         .panel-heading
diff --git a/app/views/projects/pipelines_settings/_show.html.haml b/app/views/projects/pipelines_settings/_show.html.haml
index 3b17daeb6da1ea8775d7ee9a568c32c646e5600f..255d7ef38e0c3725410d9f9522e9178603a91cd4 100644
--- a/app/views/projects/pipelines_settings/_show.html.haml
+++ b/app/views/projects/pipelines_settings/_show.html.haml
@@ -1,9 +1,9 @@
 .row.prepend-top-default
-  .col-lg-3.profile-settings-sidebar
+  .col-lg-4.profile-settings-sidebar
     %h4.prepend-top-0
       Pipelines
-  .col-lg-9
-    = form_for @project, url: namespace_project_pipelines_settings_path(@project.namespace.becomes(Namespace), @project) do |f|
+  .col-lg-8
+    = form_for @project, url: project_pipelines_settings_path(@project) do |f|
       %fieldset.builds-feature
         - unless @repository.gitlab_ci_yml
           .form-group
@@ -45,6 +45,14 @@
             Per job in minutes. If a job passes this threshold, it will be marked as failed
             = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'timeout'), target: '_blank'
 
+        %hr
+        .form-group
+          = f.label :ci_config_path, 'Custom CI config path', class: 'label-light'
+          = f.text_field :ci_config_path, class: 'form-control', placeholder: '.gitlab-ci.yml'
+          %p.help-block
+            The path to CI config file. Defaults to <code>.gitlab-ci.yml</code>
+            = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'custom-ci-config-path'), target: '_blank'
+
         %hr
         .form-group
           .checkbox
diff --git a/app/views/projects/project_members/_index.html.haml b/app/views/projects/project_members/_index.html.haml
index cfae371e169d5d8e5b41e74c6c25508b7e3a9368..fa99610c0bed8d55b094fadf3971aeab04425818 100644
--- a/app/views/projects/project_members/_index.html.haml
+++ b/app/views/projects/project_members/_index.html.haml
@@ -1,5 +1,5 @@
 .row.prepend-top-default
-  .col-lg-3.settings-sidebar
+  .col-lg-4.settings-sidebar
     %h4.prepend-top-0
       Project members
     - if can?(current_user, :admin_project_member, @project)
@@ -13,7 +13,7 @@
         %i Masters
         or
         %i Owners
-  .col-lg-9
+  .col-lg-8
     .light
       - if can?(current_user, :admin_project_member, @project)
         %ul.nav-links.project-member-tabs{ role: 'tablist' }
diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml
index 247c4bdbe2de3bad481bf8f3cad08a0bb8240c7e..bf5b11ea30ce4d44e08cbcdfcea15040135db4d0 100644
--- a/app/views/projects/project_members/_new_project_member.html.haml
+++ b/app/views/projects/project_members/_new_project_member.html.haml
@@ -1,12 +1,14 @@
 .row
   .col-sm-12
-    = form_for @project_member, as: :project_member, url: namespace_project_project_members_path(@project.namespace, @project), html: { class: 'users-project-form' } do |f|
+    = form_for @project_member, as: :project_member, url: project_project_members_path(@project), html: { class: 'users-project-form' } do |f|
       .form-group
         = label_tag :user_ids, "Select members to invite", class: "label-light"
         = users_select_tag(:user_ids, multiple: true, class: "input-clamp", scope: :all, email_user: true, placeholder: "Search for members to update or invite")
       .form-group
         = label_tag :access_level, "Choose a role permission", class: "label-light"
-        = select_tag :access_level, options_for_select(ProjectMember.access_level_roles, @project_member.access_level), class: "form-control project-access-select"
+        .select-wrapper
+          = select_tag :access_level, options_for_select(ProjectMember.access_level_roles, @project_member.access_level), class: "form-control project-access-select select-control"
+          = icon('chevron-down')
         .help-block.append-bottom-10
           = link_to "Read more", help_page_path("user/permissions"), class: "vlink"
           about role permissions
@@ -16,4 +18,4 @@
           = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Expiration date'
           %i.clear-icon.js-clear-input
       = f.submit "Add to project", class: "btn btn-create"
-      = link_to "Import", import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-default", title: "Import members from another project"
+      = link_to "Import", import_project_project_members_path(@project), class: "btn btn-default", title: "Import members from another project"
diff --git a/app/views/projects/project_members/_new_shared_group.html.haml b/app/views/projects/project_members/_new_shared_group.html.haml
index b7cc8dd7062a1671b9894aff84e3bbe21fea3d92..c10ef648a8f6ed086211160b88438c6f43350899 100644
--- a/app/views/projects/project_members/_new_shared_group.html.haml
+++ b/app/views/projects/project_members/_new_shared_group.html.haml
@@ -1,6 +1,6 @@
 .row
   .col-sm-12
-    = form_tag namespace_project_group_links_path(@project.namespace, @project), class: 'js-requires-input', method: :post do
+    = form_tag project_group_links_path(@project), class: 'js-requires-input', method: :post do
       .form-group
         = label_tag :link_group_id, "Select a group to share with", class: "label-light"
         = groups_select_tag(:link_group_id, data: { skip_groups: @skip_groups }, class: "input-clamp", required: true)
@@ -8,7 +8,7 @@
         = label_tag :link_group_access, "Max access level", class: "label-light"
         .select-wrapper
           = select_tag :link_group_access, options_for_select(ProjectGroupLink.access_options, ProjectGroupLink.default_access), class: "form-control select-control"
-          = icon('caret-down')
+          = icon('chevron-down')
         .help-block.append-bottom-10
           = link_to "Read more", help_page_path("user/permissions"), class: "vlink"
           about role permissions
diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml
index 7b1a26043e1560fc31161c09f304e3a06b6becc4..7ed467c884194c15e5f81cc4e9bcd82975d72096 100644
--- a/app/views/projects/project_members/_team.html.haml
+++ b/app/views/projects/project_members/_team.html.haml
@@ -5,7 +5,7 @@
       %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 flex-project-members-form'  do
+    = form_tag project_settings_members_path(@project), method: :get, class: 'form-inline member-search-form flex-project-members-form'  do
       .form-group
         = search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false }
         %button.member-search-btn{ type: "submit", "aria-label" => "Submit search" }
diff --git a/app/views/projects/project_members/import.html.haml b/app/views/projects/project_members/import.html.haml
index 42ce4f8001b8dce7980fe4f36d916498f4a80646..03b33eb2da7cf005ec3cc10212bcae6277275b99 100644
--- a/app/views/projects/project_members/import.html.haml
+++ b/app/views/projects/project_members/import.html.haml
@@ -5,11 +5,11 @@
 %p.light
   Only project members will be imported. Group members will be skipped.
 %hr
-= form_tag apply_import_namespace_project_project_members_path(@project.namespace, @project), method: 'post', class: 'form-horizontal' do
+= form_tag apply_import_project_project_members_path(@project), method: 'post', class: 'form-horizontal' do
   .form-group
     = label_tag :source_project_id, "Project", class: 'control-label'
     .col-sm-10= select_tag(:source_project_id, options_from_collection_for_select(current_user.authorized_projects, :id, :name_with_namespace), prompt: "Select project", class: "select2 lg", required: true)
 
   .form-actions
     = button_tag 'Import project members', class: "btn btn-create"
-    = link_to "Cancel", namespace_project_settings_members_path(@project.namespace, @project), class: "btn btn-cancel"
+    = link_to "Cancel", project_settings_members_path(@project), class: "btn btn-cancel"
diff --git a/app/views/projects/protected_branches/_index.html.haml b/app/views/projects/protected_branches/_index.html.haml
index 9af676497415bc70176e162dfab2e7d4426d9d41..5d2422bdf54d356422949e4b252cf7d9b15f5c0f 100644
--- a/app/views/projects/protected_branches/_index.html.haml
+++ b/app/views/projects/protected_branches/_index.html.haml
@@ -7,7 +7,7 @@
     %h4
       Protected Branches
     %button.btn.js-settings-toggle
-      = expanded ? 'Close' : 'Expand'
+      = expanded ? 'Collapse' : 'Expand'
     %p
       Keep stable branches secure and force developers to use merge requests.
   .settings-content.no-animate{ class: ('expanded' if expanded) }
diff --git a/app/views/projects/protected_branches/_matching_branch.html.haml b/app/views/projects/protected_branches/_matching_branch.html.haml
index 27896272733510c473db83418c17400900eb18a1..98793d632e6b4666661f441b89bcd21827efce2f 100644
--- a/app/views/projects/protected_branches/_matching_branch.html.haml
+++ b/app/views/projects/protected_branches/_matching_branch.html.haml
@@ -6,5 +6,5 @@
       %span.label.label-info.prepend-left-5 default
   %td
     - commit = @project.commit(matching_branch.name)
-    = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit-sha')
+    = link_to(commit.short_id, project_commit_path(@project, commit.id), class: 'commit-sha')
     = time_ago_with_tooltip(commit.committed_date)
diff --git a/app/views/projects/protected_branches/_protected_branch.html.haml b/app/views/projects/protected_branches/_protected_branch.html.haml
index 0f80de94392c95830850647ed5206370d856107f..e4dadc42cc001d909fe7a3b492fcfd66a026508f 100644
--- a/app/views/projects/protected_branches/_protected_branch.html.haml
+++ b/app/views/projects/protected_branches/_protected_branch.html.haml
@@ -1,4 +1,4 @@
-%tr.js-protected-branch-edit-form{ data: { url: namespace_project_protected_branch_path(@project.namespace, @project, protected_branch) } }
+%tr.js-protected-branch-edit-form{ data: { url: project_protected_branch_path(@project, protected_branch) } }
   %td
     %span.ref-name= protected_branch.name
 
@@ -7,10 +7,10 @@
   %td
     - if protected_branch.wildcard?
       - matching_branches = protected_branch.matching(repository.branches)
-      = link_to pluralize(matching_branches.count, "matching branch"), namespace_project_protected_branch_path(@project.namespace, @project, protected_branch)
+      = link_to pluralize(matching_branches.count, "matching branch"), project_protected_branch_path(@project, protected_branch)
     - else
       - if commit = protected_branch.commit
-        = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit-sha')
+        = link_to(commit.short_id, project_commit_path(@project, commit.id), class: 'commit-sha')
         = time_ago_with_tooltip(commit.committed_date)
       - else
         (branch was removed from repository)
diff --git a/app/views/projects/protected_tags/_index.html.haml b/app/views/projects/protected_tags/_index.html.haml
index 976e1d7e93f840604869d34f6cab74e571680a69..8250f692a697a5869778afcc8a58e5b60d7ce580 100644
--- a/app/views/projects/protected_tags/_index.html.haml
+++ b/app/views/projects/protected_tags/_index.html.haml
@@ -7,7 +7,7 @@
     %h4
       Protected Tags
     %button.btn.js-settings-toggle
-      = expanded ? 'Close' : 'Expand'
+      = expanded ? 'Collapse' : 'Expand'
     %p
       Limit access to creating and updating tags.
   .settings-content.no-animate{ class: ('expanded' if expanded) }
diff --git a/app/views/projects/protected_tags/_matching_tag.html.haml b/app/views/projects/protected_tags/_matching_tag.html.haml
index f17353df1221a7bbd4f8f943f48e850c210572ac..05f102d1ca399cf7191c49094c4f3f76b751f682 100644
--- a/app/views/projects/protected_tags/_matching_tag.html.haml
+++ b/app/views/projects/protected_tags/_matching_tag.html.haml
@@ -6,5 +6,5 @@
       %span.label.label-info.prepend-left-5 default
   %td
     - commit = @project.commit(matching_tag.name)
-    = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit-sha')
+    = link_to(commit.short_id, project_commit_path(@project, commit.id), class: 'commit-sha')
     = time_ago_with_tooltip(commit.committed_date)
diff --git a/app/views/projects/protected_tags/_protected_tag.html.haml b/app/views/projects/protected_tags/_protected_tag.html.haml
index f11ce0483a96a8deb50aa46e49489cb937c25a3e..5162da5e429357aef1d03f77d961ba9ffcfb6f2e 100644
--- a/app/views/projects/protected_tags/_protected_tag.html.haml
+++ b/app/views/projects/protected_tags/_protected_tag.html.haml
@@ -1,4 +1,4 @@
-%tr.js-protected-tag-edit-form{ data: { url: namespace_project_protected_tag_path(@project.namespace, @project, protected_tag) } }
+%tr.js-protected-tag-edit-form{ data: { url: project_protected_tag_path(@project, protected_tag) } }
   %td
     %span.ref-name= protected_tag.name
 
@@ -7,10 +7,10 @@
   %td
     - if protected_tag.wildcard?
       - matching_tags = protected_tag.matching(repository.tags)
-      = link_to pluralize(matching_tags.count, "matching tag"), namespace_project_protected_tag_path(@project.namespace, @project, protected_tag)
+      = link_to pluralize(matching_tags.count, "matching tag"), project_protected_tag_path(@project, protected_tag)
     - else
       - if commit = protected_tag.commit
-        = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit-sha')
+        = link_to(commit.short_id, project_commit_path(@project, commit.id), class: 'commit-sha')
         = time_ago_with_tooltip(commit.committed_date)
       - else
         (tag was removed from repository)
diff --git a/app/views/projects/registry/repositories/_image.html.haml b/app/views/projects/registry/repositories/_image.html.haml
index 8bc78f8d0185e94d5dd998218d766eb9771a86aa..a0535edafc36998510de21dbb5c940f8a261a093 100644
--- a/app/views/projects/registry/repositories/_image.html.haml
+++ b/app/views/projects/registry/repositories/_image.html.haml
@@ -6,13 +6,14 @@
 
     = clipboard_button(clipboard_text: "docker pull #{image.location}")
 
-    .controls.hidden-xs.pull-right
-      = link_to namespace_project_container_registry_path(@project.namespace, @project, image),
-                class: 'btn btn-remove has-tooltip',
-                title: 'Remove repository',
-                data: { confirm: 'Are you sure?' },
-                method: :delete do
-        = icon('trash cred', 'aria-hidden': 'true')
+    - if can?(current_user, :update_container_image, @project)
+      .controls.hidden-xs.pull-right
+        = link_to project_container_registry_path(@project, image),
+                  class: 'btn btn-remove has-tooltip',
+                  title: 'Remove repository',
+                  data: { confirm: 'Are you sure?' },
+                  method: :delete do
+          = icon('trash cred', 'aria-hidden': 'true')
 
   .container-image-tags.js-toggle-content.hide
     - if image.has_tags?
@@ -29,4 +30,3 @@
           = render partial: 'tag', collection: image.tags
     - else
       .nothing-here-block No tags in Container Registry for this container image.
-
diff --git a/app/views/projects/registry/repositories/_tag.html.haml b/app/views/projects/registry/repositories/_tag.html.haml
index 378a23f07e6fc0941b265ca809c50d6f3dc6d4bc..0b082a2137f380e7a2d6d081b7e0a29c8d041cd3 100644
--- a/app/views/projects/registry/repositories/_tag.html.haml
+++ b/app/views/projects/registry/repositories/_tag.html.haml
@@ -25,7 +25,7 @@
   - if can?(current_user, :update_container_image, @project)
     %td.content
       .controls.hidden-xs.pull-right
-        = link_to namespace_project_registry_repository_tag_path(@project.namespace, @project, tag.repository, tag.name),
+        = link_to project_registry_repository_tag_path(@project, tag.repository, tag.name),
                   method: :delete,
                   class: 'btn btn-remove has-tooltip',
                   title: 'Remove tag',
diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml
index 93ee9382a6e0956321fe15dc4d84020379abfc31..0a5a38a3694cef4cb789696d1c4a825a116f82e1 100644
--- a/app/views/projects/releases/edit.html.haml
+++ b/app/views/projects/releases/edit.html.haml
@@ -10,11 +10,11 @@
         %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|
+  = form_for(@release, method: :put, url: project_tag_release_path(@project, @tag.name), html: { class: 'form-horizontal common-note-form release-form js-quick-submit' }) do |f|
     = render layout: 'projects/md_preview', locals: { url: preview_markdown_path(@project), referenced_users: true } do
       = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: "Write your release notes or drag files here..."
       = render 'shared/notes/hints'
     .error-alert
     .prepend-top-default
       = f.submit 'Save changes', class: 'btn btn-save'
-      = link_to "Cancel", namespace_project_tag_path(@project.namespace, @project, @tag.name), class: "btn btn-default btn-cancel"
+      = link_to "Cancel", project_tag_path(@project, @tag.name), class: "btn btn-default btn-cancel"
diff --git a/app/views/projects/remove_fork.js.haml b/app/views/projects/remove_fork.js.haml
index 17b9fecfeb162f8f3ac88523a7fbe336c4d46516..6d083c5c5169bcacfb847a76bc9ecf9c1334b04b 100644
--- a/app/views/projects/remove_fork.js.haml
+++ b/app/views/projects/remove_fork.js.haml
@@ -1,2 +1,2 @@
 :plain
-    location.href = "#{edit_namespace_project_path(@project.namespace, @project)}";
+    location.href = "#{edit_project_path(@project)}";
diff --git a/app/views/projects/repositories/_feed.html.haml b/app/views/projects/repositories/_feed.html.haml
index d9c39fb87b76fcef28313effeb8268f0f38c9d4f..170f9e259df72b3343d4fdde4a47fee06924a0ff 100644
--- a/app/views/projects/repositories/_feed.html.haml
+++ b/app/views/projects/repositories/_feed.html.haml
@@ -1,7 +1,7 @@
 - commit = update
 %tr
   %td
-    = link_to namespace_project_commits_path(@project.namespace, @project, commit.head.name) do
+    = link_to project_commits_path(@project, commit.head.name) do
       %strong
         = commit.head.name
       - if @project.root_ref?(commit.head.name)
@@ -9,7 +9,7 @@
 
   %td
     %div
-      = link_to namespace_project_commits_path(@project.namespace, @project, commit.id) do
+      = link_to project_commits_path(@project, commit.id) do
         %code= commit.short_id
       = image_tag avatar_icon(commit.author_email), class: "", width: 16, alt: ''
       = markdown(truncate(commit.title, length: 40), pipeline: :single_line, author: commit.author)
diff --git a/app/views/projects/runners/_runner.html.haml b/app/views/projects/runners/_runner.html.haml
index 674f87e82205f6bed3d3b8c7e11d3f7dd5d4ba52..abc97bcdff580248870de1ec6ea1544e81599ea3 100644
--- a/app/views/projects/runners/_runner.html.haml
+++ b/app/views/projects/runners/_runner.html.haml
@@ -9,7 +9,7 @@
         = icon('lock', class: 'has-tooltip', title: 'Locked to current projects')
 
       %small
-        = link_to edit_namespace_project_runner_path(@project.namespace, @project, runner) do
+        = link_to edit_project_runner_path(@project, runner) do
           %i.fa.fa-edit.btn
     - else
       %span.commit-sha
@@ -21,7 +21,7 @@
           = link_to 'Remove Runner', runner_path(runner), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
         - else
           - runner_project = @project.runner_projects.find_by(runner_id: runner)
-          = link_to 'Disable for this project', namespace_project_runner_project_path(@project.namespace, @project, runner_project), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
+          = link_to 'Disable for this project', project_runner_project_path(@project, runner_project), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
       - elsif runner.specific?
         = form_for [@project.namespace.becomes(Namespace), @project, @project.runner_projects.new] do |f|
           = f.hidden_field :runner_id, value: runner.id
diff --git a/app/views/projects/runners/_shared_runners.html.haml b/app/views/projects/runners/_shared_runners.html.haml
index 0671dd66e78d29d818a3998eb081be52197fbad7..a4e820628f3641b317407dcdd38ce2040faa22cc 100644
--- a/app/views/projects/runners/_shared_runners.html.haml
+++ b/app/views/projects/runners/_shared_runners.html.haml
@@ -9,10 +9,10 @@
     on GitLab.com).
   %hr
   - if @project.shared_runners_enabled?
-    = link_to toggle_shared_runners_namespace_project_runners_path(@project.namespace, @project), class: 'btn btn-warning', method: :post do
+    = link_to toggle_shared_runners_project_runners_path(@project), class: 'btn btn-warning', method: :post do
       Disable shared Runners
   - else
-    = link_to toggle_shared_runners_namespace_project_runners_path(@project.namespace, @project), class: 'btn btn-success', method: :post do
+    = link_to toggle_shared_runners_project_runners_path(@project), class: 'btn btn-success', method: :post do
       Enable shared Runners
   &nbsp; for this project
 
diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml
index 9167789a69d169dc98b723e7c749d97723739617..7eab428bb2e2ac5b1da88bf5874012b88b30b6ca 100644
--- a/app/views/projects/services/_form.html.haml
+++ b/app/views/projects/services/_form.html.haml
@@ -9,7 +9,7 @@
 
     %p= @service.description
   .col-lg-9
-    = form_for(@service, as: :service, url: namespace_project_service_path(@project.namespace, @project, @service.to_param), method: :put, html: { class: 'gl-show-field-errors form-horizontal js-integration-settings-form', data: { 'can-test' => @service.can_test?, 'test-url' => test_namespace_project_service_path } }) do |form|
+    = form_for(@service, as: :service, url: project_service_path(@project, @service.to_param), method: :put, html: { class: 'gl-show-field-errors form-horizontal js-integration-settings-form', data: { 'can-test' => @service.can_test?, 'test-url' => test_project_service_path(@project, @service) } }) do |form|
       = render 'shared/service_settings', form: form, subject: @service
       .footer-block.row-content-block
         %button.btn.btn-save{ type: 'submit' }
@@ -22,4 +22,8 @@
             - disabled_class = 'disabled'
             - disabled_title = @service.disabled_title
 
-        = link_to 'Cancel', namespace_project_settings_integrations_path(@project.namespace, @project), class: 'btn btn-cancel'
+        = link_to 'Cancel', project_settings_integrations_path(@project), class: 'btn btn-cancel'
+
+- if lookup_context.template_exists?('show', "projects/services/#{@service.to_param}", true)
+  %hr
+  = render "projects/services/#{@service.to_param}/show"
diff --git a/app/views/projects/services/_index.html.haml b/app/views/projects/services/_index.html.haml
index 86d5a0ec7b821a3247184615c261c19e1473eb6f..915c6b22162a5122d00d79836f3303d0e1df6931 100644
--- a/app/views/projects/services/_index.html.haml
+++ b/app/views/projects/services/_index.html.haml
@@ -1,9 +1,9 @@
 .row.prepend-top-default.append-bottom-default
-  .col-lg-3
+  .col-lg-4
     %h4.prepend-top-0
       Project services
     %p Project services allow you to integrate GitLab with other applications
-  .col-lg-9
+  .col-lg-8
     %table.table
       %colgroup
         %col
@@ -21,7 +21,7 @@
           %td{ "aria-label" => "#{service.title}: status " + (service.activated? ? "on" : "off") }
             = boolean_to_icon service.activated?
           %td
-            = link_to edit_namespace_project_service_path(@project.namespace, @project, service.to_param) do
+            = link_to edit_project_service_path(@project, service.to_param) do
               %strong= service.title
           %td.hidden-xs
             = service.description
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 fcc91be11cd3de593f5075e5125f1d9c04e7a40f..44c0b7a90dc0fa36d367b3982387380a97dae8a3 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
@@ -2,6 +2,6 @@
   - unless @service.activated?
     .row
       .col-sm-9.col-sm-offset-3
-        = link_to new_namespace_project_mattermost_path(@project.namespace, @project), class: 'btn btn-lg' do
+        = link_to new_project_mattermost_path(@project), class: 'btn btn-lg' do
           = custom_icon('mattermost_logo', size: 15)
           Add to Mattermost
diff --git a/app/views/projects/services/prometheus/_show.html.haml b/app/views/projects/services/prometheus/_show.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..0996ec06ab7eca3c7d7a55edb14408bfd89d9379
--- /dev/null
+++ b/app/views/projects/services/prometheus/_show.html.haml
@@ -0,0 +1,45 @@
+- content_for :page_specific_javascripts do
+  = webpack_bundle_tag('prometheus_metrics')
+
+.row.prepend-top-default.append-bottom-default.prometheus-metrics-monitoring.js-prometheus-metrics-monitoring
+  .col-lg-3
+    %h4.prepend-top-0
+      Metrics
+    %p
+      Metrics are automatically configured and monitored
+      based on a library of metrics from popular exporters.
+      = link_to 'More information', '#'
+
+  .col-lg-9
+    .panel.panel-default.js-panel-monitored-metrics{ data: { "active-metrics" => "#{project_prometheus_active_metrics_path(@project, :json)}" } }
+      .panel-heading
+        %h3.panel-title
+          Monitored
+          %span.badge.js-monitored-count 0
+      .panel-body
+        .loading-metrics.text-center.js-loading-metrics
+          = icon('spinner spin 3x', class: 'metrics-load-spinner')
+          %p Finding and configuring metrics...
+        .empty-metrics.text-center.hidden.js-empty-metrics
+          = custom_icon('icon_empty_metrics')
+          %p No metrics are being monitored. To start monitoring, deploy to an environment.
+          = link_to project_environments_path(@project), title: 'View environments', class: 'btn btn-success' do
+            View environments
+        %ul.list-unstyled.metrics-list.hidden.js-metrics-list
+
+    .panel.panel-default.hidden.js-panel-missing-env-vars
+      .panel-heading
+        %h3.panel-title
+          = icon('caret-right lg fw', class: 'panel-toggle js-panel-toggle', 'aria-label' => 'Toggle panel')
+          Missing environment variable
+          %span.badge.js-env-var-count 0
+      .panel-body.hidden
+        .flash-container
+          .flash-notice
+            .flash-text
+              To set up automatic monitoring, add the environment variable
+              %code
+                $CI_ENVIRONMENT_SLUG
+              to exporter&rsquo;s queries.
+              = link_to 'More information', '#'
+        %ul.list-unstyled.metrics-list.js-missing-var-metrics-list
diff --git a/app/views/projects/settings/_head.html.haml b/app/views/projects/settings/_head.html.haml
index 00bd563999f7acaa42933c5cd171ad3c094f0b4f..b5773acb5a4d15e2a368f9c0367f0ee1e4881d6e 100644
--- a/app/views/projects/settings/_head.html.haml
+++ b/app/views/projects/settings/_head.html.haml
@@ -19,16 +19,16 @@
               %span
                 Integrations
           = nav_link(controller: :repository) do
-            = link_to namespace_project_settings_repository_path(@project.namespace, @project), title: 'Repository' do
+            = link_to project_settings_repository_path(@project), title: 'Repository' do
               %span
                 Repository
           - if @project.feature_available?(:builds, current_user)
             = nav_link(controller: :ci_cd) do
-              = link_to namespace_project_settings_ci_cd_path(@project.namespace, @project), title: 'Pipelines' do
+              = link_to project_settings_ci_cd_path(@project), title: 'Pipelines' do
                 %span
                   Pipelines
           - if Gitlab.config.pages.enabled
             = nav_link(controller: :pages) do
-              = link_to namespace_project_pages_path(@project.namespace, @project), title: 'Pages' do
+              = link_to project_pages_path(@project), title: 'Pages' do
                 %span
                   Pages
diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml
index e8d2e91bd76978f157dd77cbbac2a790a0a575c7..00ccc3ec41ef455bfccc93cd46f93a165ffc91ce 100644
--- a/app/views/projects/settings/ci_cd/show.html.haml
+++ b/app/views/projects/settings/ci_cd/show.html.haml
@@ -1,3 +1,4 @@
+- @content_class = "limit-container-width" unless fluid_layout
 - page_title "Pipelines"
 = render "projects/settings/head"
 
diff --git a/app/views/projects/settings/integrations/_project_hook.html.haml b/app/views/projects/settings/integrations/_project_hook.html.haml
index a6640592dba1ec6b6ca8dacc1cfbdda8c194e791..00700e286c3bce93125e0950a6f8fe433c353a9f 100644
--- a/app/views/projects/settings/integrations/_project_hook.html.haml
+++ b/app/views/projects/settings/integrations/_project_hook.html.haml
@@ -9,8 +9,8 @@
     .col-md-4.col-lg-5.text-right-lg.prepend-top-5
       %span.append-right-10.inline
         SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
-      = link_to "Edit", edit_namespace_project_hook_path(@project.namespace, @project, hook), class: "btn btn-sm"
-      = link_to "Test", test_namespace_project_hook_path(@project.namespace, @project, hook), class: "btn btn-sm"
-      = link_to namespace_project_hook_path(@project.namespace, @project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-transparent" do
+      = link_to "Edit", edit_project_hook_path(@project, hook), class: "btn btn-sm"
+      = link_to "Test", test_project_hook_path(@project, hook), class: "btn btn-sm"
+      = link_to project_hook_path(@project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-transparent" do
         %span.sr-only Remove
         = icon('trash')
diff --git a/app/views/projects/settings/integrations/show.html.haml b/app/views/projects/settings/integrations/show.html.haml
index f69992566b5edaf472ee853a69263086d7a32663..1d1d08492896fd9bcdf4675c15ed5267572c45fe 100644
--- a/app/views/projects/settings/integrations/show.html.haml
+++ b/app/views/projects/settings/integrations/show.html.haml
@@ -1,3 +1,4 @@
+- @content_class = "limit-container-width" unless fluid_layout
 - page_title 'Integrations'
 = render "projects/settings/head"
 = render 'projects/hooks/index'
diff --git a/app/views/projects/settings/members/show.html.haml b/app/views/projects/settings/members/show.html.haml
index 343807b87cd4d42bba5b02ba23c2db6bfbe5d3e3..1e7695ac3970a60b334c969cadcb45af45b5fbce 100644
--- a/app/views/projects/settings/members/show.html.haml
+++ b/app/views/projects/settings/members/show.html.haml
@@ -1,3 +1,5 @@
+- @content_class = "limit-container-width" unless fluid_layout
+
 - page_title "Members"
 = render "projects/settings/head"
 
diff --git a/app/views/projects/show.atom.builder b/app/views/projects/show.atom.builder
index ed34f5c052015295b6b4d9c94809a74f8cf8b277..39f8cb9a0e0f197699017b0c598b795498707d56 100644
--- a/app/views/projects/show.atom.builder
+++ b/app/views/projects/show.atom.builder
@@ -1,7 +1,7 @@
 xml.title   "#{@project.name} activity"
-xml.link    href: namespace_project_url(@project.namespace, @project, rss_url_options), rel: "self", type: "application/atom+xml"
-xml.link    href: namespace_project_url(@project.namespace, @project), rel: "alternate", type: "text/html"
-xml.id      namespace_project_url(@project.namespace, @project)
+xml.link    href: project_url(@project, rss_url_options), rel: "self", type: "application/atom+xml"
+xml.link    href: project_url(@project), rel: "alternate", type: "text/html"
+xml.id      project_url(@project)
 xml.updated @events[0].updated_at.xmlschema if @events[0]
 
 xml << render(@events) if @events.any?
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 7447197ed892ebf5e5e72f70083e05995c468ff9..ea780b1cb83edc90cf822b792b3d101e549f6564 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -1,7 +1,7 @@
 - @no_container = true
 
 = content_for :meta_tags do
-  = auto_discovery_link_tag(:atom, namespace_project_path(@project.namespace, @project, rss_url_options), title: "#{@project.name} activity")
+  = auto_discovery_link_tag(:atom, project_path(@project, rss_url_options), title: "#{@project.name} activity")
 
 = content_for :flash_message do
   - if current_user && can?(current_user, :download_code, @project)
@@ -16,16 +16,16 @@
   %nav.project-stats{ class: container_class }
     %ul.nav
       %li
-        = link_to project_files_path(@project) do
+        = link_to project_tree_path(@project) do
           #{_('Files')} (#{storage_counter(@project.statistics.total_repository_size)})
       %li
-        = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
+        = link_to project_commits_path(@project, current_ref) do
           #{n_('Commit', 'Commits', @project.statistics.commit_count)} (#{number_with_delimiter(@project.statistics.commit_count)})
-      %l
-        = link_to namespace_project_branches_path(@project.namespace, @project) do
+      %li
+        = link_to project_branches_path(@project) do
           #{n_('Branch', 'Branches', @repository.branch_count)} (#{number_with_delimiter(@repository.branch_count)})
       %li
-        = link_to namespace_project_tags_path(@project.namespace, @project) do
+        = link_to project_tags_path(@project) do
           #{n_('Tag', 'Tags', @repository.tag_count)} (#{number_with_delimiter(@repository.tag_count)})
 
       - if default_project_view != 'readme' && @repository.readme
@@ -73,7 +73,7 @@
             = link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml', commit_message: 'Set up auto deploy', branch_name: 'auto-deploy', context: 'autodeploy') do
               #{ _('Set up auto deploy') }
 
-%div{ class: container_class }
+%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
   - if @project.archived?
     .text-warning.center.prepend-top-20
       %p
diff --git a/app/views/projects/snippets/_actions.html.haml b/app/views/projects/snippets/_actions.html.haml
index 34ee4ff1937d254f14201a4f5bd4fb390f89efa4..f09871c7fcc918cdd1763b1a1afb0fd084c9064d 100644
--- a/app/views/projects/snippets/_actions.html.haml
+++ b/app/views/projects/snippets/_actions.html.haml
@@ -2,16 +2,16 @@
 
 .hidden-xs
   - if can?(current_user, :update_project_snippet, @snippet)
-    = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped" do
+    = link_to edit_project_snippet_path(@project, @snippet), class: "btn btn-grouped" do
       Edit
   - if can?(current_user, :update_project_snippet, @snippet)
-    = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-inverted btn-remove", title: 'Delete Snippet' do
+    = link_to project_snippet_path(@project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-inverted btn-remove", title: 'Delete Snippet' do
       Delete
   - 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
+    = link_to new_project_snippet_path(@project), class: 'btn btn-grouped btn-inverted btn-create', title: "New snippet" do
       New snippet
   - if @snippet.submittable_as_spam_by?(current_user)
-    = 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'
+    = link_to 'Submit as spam', mark_as_spam_project_snippet_path(@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" } }
@@ -21,16 +21,16 @@
       %ul
         - if can?(current_user, :create_project_snippet, @project)
           %li
-            = link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New snippet" do
+            = link_to new_project_snippet_path(@project), title: "New snippet" do
               New snippet
         - if can?(current_user, :update_project_snippet, @snippet)
           %li
-            = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
+            = link_to project_snippet_path(@project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
               Delete
         - if can?(current_user, :update_project_snippet, @snippet)
           %li
-            = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet) do
+            = link_to edit_project_snippet_path(@project, @snippet) do
               Edit
         - if @snippet.submittable_as_spam_by?(current_user)
           %li
-            = link_to 'Submit as spam', mark_as_spam_namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :post
+            = link_to 'Submit as spam', mark_as_spam_project_snippet_path(@project, @snippet), method: :post
diff --git a/app/views/projects/snippets/edit.html.haml b/app/views/projects/snippets/edit.html.haml
index 24b92094b7d9010cd8918034970a0dcea2cec40a..d41cc8e0425a3ceeac7eb769ec603239a573a990 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)
+= render "shared/snippets/form", url: project_snippet_path(@project, @snippet)
diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml
index 84e05cd6d88b49c1fd34282af7dc47a6b03d09d2..4f8ce526c83b9c689596355d94a872517d80ab3d 100644
--- a/app/views/projects/snippets/index.html.haml
+++ b/app/views/projects/snippets/index.html.haml
@@ -7,13 +7,13 @@
 
     .nav-controls.hidden-xs
       - if can?(current_user, :create_project_snippet, @project)
-        = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New snippet" do
+        = link_to new_project_snippet_path(@project), class: "btn btn-new", title: "New snippet" do
           New snippet
 
 - if can?(current_user, :create_project_snippet, @project)
   .visible-xs
     &nbsp;
-    = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new btn-block", title: "New snippet" do
+    = link_to new_project_snippet_path(@project), class: "btn btn-new btn-block", title: "New snippet" do
       New snippet
 
 = render 'snippets/snippets'
diff --git a/app/views/projects/snippets/new.html.haml b/app/views/projects/snippets/new.html.haml
index cfed3a79bc5b0187eeb5576d2479ab63130346ff..d3e6b456f48d99a0d723f5e16141dc523ea8c108 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)
+= render "shared/snippets/form", url: project_snippets_path(@project, @snippet)
diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml
index 847f3c2f348385d929b4d08af05ff765694d54b7..d8e448dd2afce30194a93dd9c396c373a1e9a70b 100644
--- a/app/views/projects/snippets/show.html.haml
+++ b/app/views/projects/snippets/show.html.haml
@@ -1,3 +1,4 @@
+- @content_class = "limit-container-width limited-inner-width-container" unless fluid_layout
 - page_title "#{@snippet.title} (#{@snippet.to_reference})", "Snippets"
 
 = render 'shared/snippets/header'
@@ -9,4 +10,4 @@
   .row-content-block.top-block.content-component-block
     = render 'award_emoji/awards_block', awardable: @snippet, inline: true
 
-  #notes= render "shared/notes/notes_with_form", :autocomplete => true
+  #notes.limited-width-notes= render "shared/notes/notes_with_form", :autocomplete => true
diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml
index 44cb734d7b9da9570483df8e44e1e4177dfa4f46..468ab922542fade72c6fc7b5bd59a59518b5dc76 100644
--- a/app/views/projects/tags/_tag.html.haml
+++ b/app/views/projects/tags/_tag.html.haml
@@ -2,7 +2,7 @@
 - release = @releases.find { |release| release.tag == tag.name }
 %li.flex-row
   .row-main-content.str-truncated
-    = link_to namespace_project_tag_path(@project.namespace, @project, tag.name), class: 'item-title ref-name' do
+    = link_to project_tag_path(@project, tag.name), class: 'item-title ref-name' do
       = icon('tag')
       = tag.name
 
@@ -29,9 +29,9 @@
     = render 'projects/buttons/download', project: @project, ref: tag.name, pipeline: @tags_pipelines[tag.name]
 
     - if can?(current_user, :push_code, @project)
-      = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn has-tooltip', title: "Edit release notes", data: { container: "body" } do
+      = link_to edit_project_tag_release_path(@project, tag.name), class: 'btn has-tooltip', title: "Edit release notes", data: { container: "body" } do
         = icon("pencil")
 
     - if can?(current_user, :admin_project, @project)
-      = link_to namespace_project_tag_path(@project.namespace, @project, tag.name), class: "btn btn-remove remove-row has-tooltip #{protected_tag?(@project, tag) ? 'disabled' : ''}", title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{tag.name}' tag cannot be undone. Are you sure?", container: 'body' }, remote: true do
+      = link_to project_tag_path(@project, tag.name), class: "btn btn-remove remove-row has-tooltip #{protected_tag?(@project, tag) ? 'disabled' : ''}", title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{tag.name}' tag cannot be undone. Are you sure?", container: 'body' }, remote: true do
         = icon("trash-o")
diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml
index 56656ea3d8600e01a4be4c8966e70242b74a45c9..bf97cbc1f68a813d79ae1f1545284dffa107f681 100644
--- a/app/views/projects/tags/index.html.haml
+++ b/app/views/projects/tags/index.html.haml
@@ -24,7 +24,7 @@
             %li
               = link_to title, filter_tags_path(sort: value), class: ("is-active" if @sort == value)
       - if can?(current_user, :push_code, @project)
-        = link_to new_namespace_project_tag_path(@project.namespace, @project), class: 'btn btn-create new-tag-btn' do
+        = link_to new_project_tag_path(@project), class: 'btn btn-create new-tag-btn' do
           New tag
 
   .tags
diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml
index 52af295bddd8ffd5276c9f1a13d4fcdbba480b0d..f1bbaf40387743dfb2792fcc2db785fbcbb46b48 100644
--- a/app/views/projects/tags/new.html.haml
+++ b/app/views/projects/tags/new.html.haml
@@ -39,7 +39,7 @@
       .help-block Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page.
   .form-actions
     = button_tag 'Create tag', class: 'btn btn-create', tabindex: 3
-    = link_to 'Cancel', namespace_project_tags_path(@project.namespace, @project), class: 'btn btn-cancel'
+    = link_to 'Cancel', project_tags_path(@project), class: 'btn btn-cancel'
 
 :javascript
   window.gl = window.gl || { };
diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index 2b81ce4b9fa9234312aeb24ba9208c932b1014f8..d02cd70f4c389a765a79ff6616fd5218d8f9cb4b 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -19,17 +19,17 @@
 
     .nav-controls.controls-flex
       - if can?(current_user, :push_code, @project)
-        = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn controls-item has-tooltip', title: 'Edit release notes' do
+        = link_to edit_project_tag_release_path(@project, @tag.name), class: 'btn controls-item has-tooltip', title: 'Edit release notes' do
           = icon("pencil")
-      = link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn controls-item has-tooltip', title: 'Browse files' do
+      = link_to project_tree_path(@project, @tag.name), class: 'btn controls-item has-tooltip', title: 'Browse files' do
         = icon('files-o')
-      = link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn controls-item has-tooltip', title: 'Browse commits' do
+      = link_to project_commits_path(@project, @tag.name), class: 'btn controls-item has-tooltip', title: 'Browse commits' do
         = icon('history')
       .btn-container.controls-item
         = render 'projects/buttons/download', project: @project, ref: @tag.name
       - if can?(current_user, :admin_project, @project)
         .btn-container.controls-item-full
-          = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: "btn btn-remove remove-row has-tooltip #{protected_tag?(@project, @tag) ? 'disabled' : ''}", title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
+          = link_to project_tag_path(@project, @tag.name), class: "btn btn-remove remove-row has-tooltip #{protected_tag?(@project, @tag) ? 'disabled' : ''}", title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
             %i.fa.fa-trash-o
 
     - if @tag.message.present?
diff --git a/app/views/projects/transfer.js.haml b/app/views/projects/transfer.js.haml
index 17b9fecfeb162f8f3ac88523a7fbe336c4d46516..6d083c5c5169bcacfb847a76bc9ecf9c1334b04b 100644
--- a/app/views/projects/transfer.js.haml
+++ b/app/views/projects/transfer.js.haml
@@ -1,2 +1,2 @@
 :plain
-    location.href = "#{edit_namespace_project_path(@project.namespace, @project)}";
+    location.href = "#{edit_project_path(@project)}";
diff --git a/app/views/projects/tree/_blob_item.html.haml b/app/views/projects/tree/_blob_item.html.haml
index 425b460eb093a7af430a3925e5cdc7a82dac4c8f..fd8175e1e018f11e95692316c6db70c9acc17df7 100644
--- a/app/views/projects/tree/_blob_item.html.haml
+++ b/app/views/projects/tree/_blob_item.html.haml
@@ -2,7 +2,7 @@
   %td.tree-item-file-name
     = tree_icon(type, blob_item.mode, blob_item.name)
     - file_name = blob_item.name
-    = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@id || @commit.id, blob_item.name)), title: file_name do
+    = link_to project_blob_path(@project, tree_join(@id || @commit.id, blob_item.name)), title: file_name do
       %span.str-truncated= file_name
   %td.hidden-xs.tree-commit
   %td.tree-time-ago.cgray.text-right
diff --git a/app/views/projects/tree/_readme.html.haml b/app/views/projects/tree/_readme.html.haml
index de57cd4ba0017f4e953098a6370bc33a795f6a54..4579a912f39c98a8ecbfe01e39b38d2884b7bfc9 100644
--- a/app/views/projects/tree/_readme.html.haml
+++ b/app/views/projects/tree/_readme.html.haml
@@ -1,9 +1,9 @@
 - if readme.rich_viewer
-  %article.file-holder.readme-holder
+  %article.file-holder.readme-holder{ class: ("limited-width-container" unless fluid_layout) }
     .js-file-title.file-title
       = blob_icon readme.mode, readme.name
-      = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@ref, readme.path)) do
+      = link_to project_blob_path(@project, tree_join(@ref, readme.path)) do
         %strong
           = readme.name
 
-    = render 'projects/blob/viewer', viewer: readme.rich_viewer, viewer_url: namespace_project_blob_path(@project.namespace, @project, tree_join(@ref, readme.path), viewer: :rich, format: :json)
+    = render 'projects/blob/viewer', viewer: readme.rich_viewer, viewer_url: project_blob_path(@project, tree_join(@ref, readme.path), viewer: :rich, format: :json)
diff --git a/app/views/projects/tree/_tree_commit_column.html.haml b/app/views/projects/tree/_tree_commit_column.html.haml
index 84da16b6bb15fbfc0125c3159612deeb9de93284..f3d4706809f2cb2b22a93a422564901c85d3f259 100644
--- a/app/views/projects/tree/_tree_commit_column.html.haml
+++ b/app/views/projects/tree/_tree_commit_column.html.haml
@@ -1,2 +1,2 @@
 %span.str-truncated
-  = link_to_gfm commit.full_title, namespace_project_commit_path(@project.namespace, @project, commit.id), class: "tree-commit-link"
+  = link_to_gfm commit.full_title, project_commit_path(@project, commit.id), class: "tree-commit-link"
diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml
index 7854e1305dbf411914d0818ceabe14dd1637c984..6560bd5ab3fbef2f976cc5058e461a39ebf0d28d 100644
--- a/app/views/projects/tree/_tree_content.html.haml
+++ b/app/views/projects/tree/_tree_content.html.haml
@@ -10,7 +10,7 @@
       - if @path.present?
         %tr.tree-item
           %td.tree-item-file-name
-            = link_to "..", namespace_project_tree_path(@project.namespace, @project, up_dir_path), class: 'prepend-left-10'
+            = link_to "..", project_tree_path(@project, up_dir_path), class: 'prepend-left-10'
           %td
           %td.hidden-xs
 
@@ -20,7 +20,7 @@
     = render "projects/tree/readme", readme: tree.readme
 
 - if can_edit_tree?
-  = render 'projects/blob/upload', title: _('Upload New File'), placeholder: _('Upload New File'), button_title: _('Upload file'), form_path: namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post
+  = render 'projects/blob/upload', title: _('Upload New File'), placeholder: _('Upload New File'), button_title: _('Upload file'), form_path: project_create_blob_path(@project, @id), method: :post
   = render 'projects/blob/new_dir'
 
 :javascript
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index abde2a485872ea7c1c32834b15b8789b15e35450..858418ff8df7bb084eae9ed9fd9fee4393ef26aa 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -1,79 +1,81 @@
-.tree-controls
-  = render 'projects/find_file_link'
-
-  = link_to s_('Commits|History'), namespace_project_commits_path(@project.namespace, @project, @id), class: 'btn btn-grouped'
-
-  = render 'projects/buttons/download', project: @project, ref: @ref
+.tree-ref-container
+  .tree-ref-holder
+    = render 'shared/ref_switcher', destination: 'tree', path: @path
 
-.tree-ref-holder
-  = render 'shared/ref_switcher', destination: 'tree', path: @path
-
-%ul.breadcrumb.repo-breadcrumb
-  %li
-    = link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
-      = @project.path
-  - path_breadcrumbs do |title, path|
+  %ul.breadcrumb.repo-breadcrumb
     %li
-      = link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, tree_join(@ref, path))
+      = link_to project_tree_path(@project, @ref) do
+        = @project.path
+    - path_breadcrumbs do |title, path|
+      %li
+        = link_to truncate(title, length: 40), project_tree_path(@project, tree_join(@ref, path))
 
-  - if current_user
-    %li
-      - if !on_top_of_branch?
-        %span.btn.add-to-tree.disabled.has-tooltip{ title: _("You can only add files when you are on a branch"), data: { container: 'body' } }
-          = icon('plus')
-      - else
-        %span.dropdown
-          %a.dropdown-toggle.btn.add-to-tree{ href: '#', "data-toggle" => "dropdown" }
+    - if current_user
+      %li
+        - if !on_top_of_branch?
+          %span.btn.add-to-tree.disabled.has-tooltip{ title: _("You can only add files when you are on a branch"), data: { container: 'body' } }
             = icon('plus')
-          %ul.dropdown-menu
-            - if can_edit_tree?
-              %li
-                = link_to namespace_project_new_blob_path(@project.namespace, @project, @id) do
-                  = icon('pencil fw')
-                  #{ _('New file') }
-              %li
-                = link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } do
-                  = icon('file fw')
-                  #{ _('Upload file') }
-              %li
-                = link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal' } do
-                  = icon('folder fw')
-                  #{ _('New directory') }
-            - elsif can?(current_user, :fork_project, @project)
-              %li
-                - continue_params = { to:         namespace_project_new_blob_path(@project.namespace, @project, @id),
-                                      notice:     edit_in_new_fork_notice,
-                                      notice_now: edit_in_new_fork_notice_now }
-                - fork_path = namespace_project_forks_path(@project.namespace, @project, namespace_key:  current_user.namespace.id,
-                                                                                        continue:       continue_params)
-                = link_to fork_path, method: :post do
-                  = icon('pencil fw')
-                  #{ _('New file') }
+        - else
+          %span.dropdown
+            %a.dropdown-toggle.btn.add-to-tree{ href: '#', "data-toggle" => "dropdown", "data-target" => ".add-to-tree-dropdown" }
+              = icon('plus')
+          .add-to-tree-dropdown
+            %ul.dropdown-menu
+              - if can_edit_tree?
+                %li
+                  = link_to project_new_blob_path(@project, @id) do
+                    = icon('pencil fw')
+                    #{ _('New file') }
+                %li
+                  = link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } do
+                    = icon('file fw')
+                    #{ _('Upload file') }
+                %li
+                  = link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal' } do
+                    = icon('folder fw')
+                    #{ _('New directory') }
+              - elsif can?(current_user, :fork_project, @project)
+                %li
+                  - continue_params = { to:         project_new_blob_path(@project, @id),
+                                        notice:     edit_in_new_fork_notice,
+                                        notice_now: edit_in_new_fork_notice_now }
+                  - fork_path = project_forks_path(@project, namespace_key:  current_user.namespace.id,
+                                                                                          continue:       continue_params)
+                  = link_to fork_path, method: :post do
+                    = icon('pencil fw')
+                    #{ _('New file') }
+                %li
+                  - continue_params = { to:         request.fullpath,
+                                        notice:     edit_in_new_fork_notice + " Try to upload a file again.",
+                                        notice_now: edit_in_new_fork_notice_now }
+                  - fork_path = project_forks_path(@project, namespace_key:  current_user.namespace.id,
+                                                                                          continue:       continue_params)
+                  = link_to fork_path, method: :post do
+                    = icon('file fw')
+                    #{ _('Upload file') }
+                %li
+                  - continue_params = { to:         request.fullpath,
+                                        notice:     edit_in_new_fork_notice + " Try to create a new directory again.",
+                                        notice_now: edit_in_new_fork_notice_now }
+                  - fork_path = project_forks_path(@project, namespace_key:  current_user.namespace.id,
+                                                                                          continue:       continue_params)
+                  = link_to fork_path, method: :post do
+                    = icon('folder fw')
+                    #{ _('New directory') }
+
+              %li.divider
               %li
-                - continue_params = { to:         request.fullpath,
-                                      notice:     edit_in_new_fork_notice + " Try to upload a file again.",
-                                      notice_now: edit_in_new_fork_notice_now }
-                - fork_path = namespace_project_forks_path(@project.namespace, @project, namespace_key:  current_user.namespace.id,
-                                                                                        continue:       continue_params)
-                = link_to fork_path, method: :post do
-                  = icon('file fw')
-                  #{ _('Upload file') }
+                = link_to new_project_branch_path(@project) do
+                  = icon('code-fork fw')
+                  #{ _('New branch') }
               %li
-                - continue_params = { to:         request.fullpath,
-                                      notice:     edit_in_new_fork_notice + " Try to create a new directory again.",
-                                      notice_now: edit_in_new_fork_notice_now }
-                - fork_path = namespace_project_forks_path(@project.namespace, @project, namespace_key:  current_user.namespace.id,
-                                                                                        continue:       continue_params)
-                = link_to fork_path, method: :post do
-                  = icon('folder fw')
-                  #{ _('New directory') }
+                = link_to new_project_tag_path(@project) do
+                  = icon('tags fw')
+                  #{ _('New tag') }
+
+.tree-controls
+  = render 'projects/find_file_link'
 
-            %li.divider
-            %li
-              = link_to new_namespace_project_branch_path(@project.namespace, @project) do
-                = icon('code-fork fw')
-                #{ _('New branch') }
-            %li
-              = link_to new_namespace_project_tag_path(@project.namespace, @project) do
-                = icon('tags fw')
-                #{ _('New tag') }
+  = link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn'
+
+  = render 'projects/buttons/download', project: @project, ref: @ref
diff --git a/app/views/projects/tree/_tree_item.html.haml b/app/views/projects/tree/_tree_item.html.haml
index 15c9536133cd24b62924d0e2a20b8d6cd35f4572..0c9c8750f2ca7bb82880ef55d33bdd5c2ce05627 100644
--- a/app/views/projects/tree/_tree_item.html.haml
+++ b/app/views/projects/tree/_tree_item.html.haml
@@ -2,7 +2,7 @@
   %td.tree-item-file-name
     = tree_icon(type, tree_item.mode, tree_item.name)
     - path = flatten_tree(tree_item)
-    = link_to namespace_project_tree_path(@project.namespace, @project, tree_join(@id || @commit.id, path)), title: path do
+    = link_to project_tree_path(@project, tree_join(@id || @commit.id, path)), title: path do
       %span.str-truncated= path
   %td.hidden-xs.tree-commit
   %td.tree-time-ago.text-right
diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml
index 96a08f9f8be14f99efbe8ae6a1724c7e64564669..3fb247c5ceb4399b33277ea5ed18307e0dac4a29 100644
--- a/app/views/projects/tree/show.html.haml
+++ b/app/views/projects/tree/show.html.haml
@@ -2,7 +2,7 @@
 
 - page_title @path.presence || _("Files"), @ref
 = content_for :meta_tags do
-  = auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits")
+  = auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits")
 = render "projects/commits/head"
 
 = render 'projects/last_push'
diff --git a/app/views/projects/triggers/_index.html.haml b/app/views/projects/triggers/_index.html.haml
index cc74e50a5e37ae97a3685c0c2ce60de7f6d4602d..e9a2f803eddf6353e11c9f2715217389265cd6e9 100644
--- a/app/views/projects/triggers/_index.html.haml
+++ b/app/views/projects/triggers/_index.html.haml
@@ -1,7 +1,7 @@
 .row.prepend-top-default.append-bottom-default.triggers-container
-  .col-lg-3
+  .col-lg-4
     = render "projects/triggers/content"
-  .col-lg-9
+  .col-lg-8
     .panel.panel-default
       .panel-heading
         %h4.panel-title
diff --git a/app/views/projects/triggers/_trigger.html.haml b/app/views/projects/triggers/_trigger.html.haml
index 9b5f63ae81a5c5c5f4ca7fa017123d4a57d46229..6249c32b7cc570796feb9cd0f2a8a3358924c602 100644
--- a/app/views/projects/triggers/_trigger.html.haml
+++ b/app/views/projects/triggers/_trigger.html.haml
@@ -33,10 +33,10 @@
     - take_ownership_confirmation = "By taking ownership you will bind this trigger to your user account. With this the trigger will have access to all your projects as if it was you. Are you sure?"
     - revoke_trigger_confirmation = "By revoking a trigger you will break any processes making use of it. Are you sure?"
     - if trigger.owner != current_user && can?(current_user, :manage_trigger, trigger)
-      = link_to 'Take ownership', take_ownership_namespace_project_trigger_path(@project.namespace, @project, trigger), data: { confirm: take_ownership_confirmation }, method: :post, class: "btn btn-default btn-sm btn-trigger-take-ownership"
+      = link_to 'Take ownership', take_ownership_project_trigger_path(@project, trigger), data: { confirm: take_ownership_confirmation }, method: :post, class: "btn btn-default btn-sm btn-trigger-take-ownership"
     - if can?(current_user, :admin_trigger, trigger)
-      = link_to edit_namespace_project_trigger_path(@project.namespace, @project, trigger), method: :get, title: "Edit", class: "btn btn-default btn-sm" do
+      = link_to edit_project_trigger_path(@project, trigger), method: :get, title: "Edit", class: "btn btn-default btn-sm" do
         %i.fa.fa-pencil
     - if can?(current_user, :manage_trigger, trigger)
-      = link_to namespace_project_trigger_path(@project.namespace, @project, trigger), data: { confirm: revoke_trigger_confirmation }, method: :delete, title: "Revoke", class: "btn btn-default btn-warning btn-sm btn-trigger-revoke" do
+      = link_to project_trigger_path(@project, trigger), data: { confirm: revoke_trigger_confirmation }, method: :delete, title: "Revoke", class: "btn btn-default btn-warning btn-sm btn-trigger-revoke" do
         %i.fa.fa-trash
diff --git a/app/views/projects/update.js.haml b/app/views/projects/update.js.haml
index dcf1f767bf7967a8c4f9ab97bb1ea94b211c2c5e..2c05ebe52ae028839c067839295418e72e2085c2 100644
--- a/app/views/projects/update.js.haml
+++ b/app/views/projects/update.js.haml
@@ -1,6 +1,6 @@
 - if @project.valid?
   :plain
-    location.href = "#{edit_namespace_project_path(@project.namespace, @project)}";
+    location.href = "#{edit_project_path(@project)}";
 - else
   :plain
     $(".project-edit-errors").html("#{escape_javascript(render('errors'))}");
diff --git a/app/views/projects/variables/_index.html.haml b/app/views/projects/variables/_index.html.haml
index 1b852a9c5b36e922eea0714b150e10118ba74bbc..5e6786f66985502d58d779d156acff3bd472f47b 100644
--- a/app/views/projects/variables/_index.html.haml
+++ b/app/views/projects/variables/_index.html.haml
@@ -1,7 +1,7 @@
 .row.prepend-top-default.append-bottom-default
-  .col-lg-3
+  .col-lg-4
     = render "projects/variables/content"
-  .col-lg-9
+  .col-lg-8
     %h5.prepend-top-0
       Add a variable
     = render "projects/variables/form", btn_text: "Add new variable"
diff --git a/app/views/projects/variables/_table.html.haml b/app/views/projects/variables/_table.html.haml
index 59cd3c4b59271b3fc1b55ffbded5cfe866b062ed..4ce6a82881218cd0929d75a2a676ea7ab46fcf83 100644
--- a/app/views/projects/variables/_table.html.haml
+++ b/app/views/projects/variables/_table.html.haml
@@ -18,11 +18,11 @@
             %td.variable-value{ "data-value" => variable.value }******
             %td.variable-protected= Gitlab::Utils.boolean_to_yes_no(variable.protected)
             %td.variable-menu
-              = link_to namespace_project_variable_path(@project.namespace, @project, variable), class: "btn btn-transparent btn-variable-edit" do
+              = link_to project_variable_path(@project, variable), class: "btn btn-transparent btn-variable-edit" do
                 %span.sr-only
                   Update
                 = icon("pencil")
-              = link_to namespace_project_variable_path(@project.namespace, @project, variable), class: "btn btn-transparent btn-variable-delete", method: :delete, data: { confirm: "Are you sure?" } do
+              = link_to project_variable_path(@project, variable), class: "btn btn-transparent btn-variable-delete", method: :delete, data: { confirm: "Are you sure?" } do
                 %span.sr-only
                   Remove
                 = icon("trash")
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index c10b3004bc30665b8b94a21f0e46da5afdf649f1..fc6b7a3394319bde8eca8c7436be153a9dc4d46a 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -12,7 +12,7 @@
   .form-group
     .col-sm-12= f.label :content, class: 'control-label-full-width'
     .col-sm-12
-      = render layout: 'projects/md_preview', locals: { url: namespace_project_wiki_preview_markdown_path(@project.namespace, @project, @page.slug) } do
+      = render layout: 'projects/md_preview', locals: { url: project_wiki_preview_markdown_path(@project, @page.slug) } do
         = render 'projects/zen', f: f, attr: :content, classes: 'note-textarea', placeholder: 'Write your content or drag files here...'
         = render 'shared/notes/hints'
 
@@ -36,8 +36,8 @@
     - if @page && @page.persisted?
       = f.submit 'Save changes', class: "btn-save btn"
       .pull-right
-        = link_to "Cancel", namespace_project_wiki_path(@project.namespace, @project, @page), class: "btn btn-cancel btn-grouped"
+        = link_to "Cancel", project_wiki_path(@project, @page), class: "btn btn-cancel btn-grouped"
     - else
       = f.submit 'Create page', class: "btn-create btn"
       .pull-right
-        = link_to "Cancel", namespace_project_wiki_path(@project.namespace, @project, :home), class: "btn btn-cancel"
+        = link_to "Cancel", project_wiki_path(@project, :home), class: "btn btn-cancel"
diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml
index 6a578dbf640d660e11980efcc1732f1186d459d3..3bbd8042c3a4b906fdd3380a355736952677cb60 100644
--- a/app/views/projects/wikis/_main_links.html.haml
+++ b/app/views/projects/wikis/_main_links.html.haml
@@ -2,8 +2,8 @@
   - if can?(current_user, :create_wiki, @project)
     = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
       New page
-  = link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn" do
+  = link_to project_wiki_history_path(@project, @page), class: "btn" do
     Page history
   - if can?(current_user, :create_wiki, @project) && @page.latest?
-    = link_to namespace_project_wiki_edit_path(@project.namespace, @project, @page), class: "btn js-wiki-edit" do
+    = link_to project_wiki_edit_path(@project, @page), class: "btn js-wiki-edit" do
       Edit
diff --git a/app/views/projects/wikis/_new.html.haml b/app/views/projects/wikis/_new.html.haml
index 1e553940593e531a158e48562ab53fe2a5c48cb6..13dd8461433a829dddf84c0cd007fef4243b03af 100644
--- a/app/views/projects/wikis/_new.html.haml
+++ b/app/views/projects/wikis/_new.html.haml
@@ -9,7 +9,7 @@
           .form-group
             = label_tag :new_wiki_path do
               %span Page slug
-            = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => namespace_project_wikis_path(@project.namespace, @project), autofocus: true
+            = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => project_wikis_path(@project), autofocus: true
             %span.new-wiki-page-slug-tip
               = icon('lightbulb-o')
               Tip: You can specify the full path for the new file.
diff --git a/app/views/projects/wikis/_pages_wiki_page.html.haml b/app/views/projects/wikis/_pages_wiki_page.html.haml
index 6298cf6c8dafc6365c5d15fb2b9f612da34ca33a..7c2f562d4228fec0e75b60465770577c4ba335f1 100644
--- a/app/views/projects/wikis/_pages_wiki_page.html.haml
+++ b/app/views/projects/wikis/_pages_wiki_page.html.haml
@@ -1,5 +1,5 @@
 %li
-  = link_to wiki_page.title, namespace_project_wiki_path(@project.namespace, @project, wiki_page)
+  = link_to wiki_page.title, project_wiki_path(@project, wiki_page)
   %small (#{wiki_page.format})
   .pull-right
     %small Last edited #{time_ago_with_tooltip(wiki_page.commit.authored_date)}
diff --git a/app/views/projects/wikis/_sidebar.html.haml b/app/views/projects/wikis/_sidebar.html.haml
index c2f9e65015d249edc5a1ae7b36df033dabdcfd01..62873d3aa66c7a02d5e0c23b363b7c994247dced 100644
--- a/app/views/projects/wikis/_sidebar.html.haml
+++ b/app/views/projects/wikis/_sidebar.html.haml
@@ -3,7 +3,7 @@
     %a.gutter-toggle.pull-right.visible-xs-block.visible-sm-block.js-sidebar-wiki-toggle{ href: "#" }
       = icon('angle-double-right')
 
-    - git_access_url = namespace_project_wikis_git_access_path(@project.namespace, @project)
+    - git_access_url = project_wikis_git_access_path(@project)
     = link_to git_access_url, class: active_nav_link?(path: 'wikis#git_access') ? 'active' : '' do
       = succeed '&nbsp;' do
         = icon('cloud-download')
@@ -15,7 +15,7 @@
         = render @sidebar_wiki_entries, context: 'sidebar'
 
     .block
-      = link_to namespace_project_wikis_pages_path(@project.namespace, @project), class: 'btn btn-block' do
+      = link_to project_wikis_pages_path(@project), class: 'btn btn-block' do
         More Pages
 
 = render 'projects/wikis/new'
diff --git a/app/views/projects/wikis/_sidebar_wiki_page.html.haml b/app/views/projects/wikis/_sidebar_wiki_page.html.haml
index 0a61d90177be98cea964d2a1116c2ff1c91cadfe..2423ac6abcee21a4173cf620819ce55b0404a08f 100644
--- a/app/views/projects/wikis/_sidebar_wiki_page.html.haml
+++ b/app/views/projects/wikis/_sidebar_wiki_page.html.haml
@@ -1,3 +1,3 @@
 %li{ class: active_when(params[:id] == wiki_page.slug) }
-  = link_to namespace_project_wiki_path(@project.namespace, @project, wiki_page) do
+  = link_to project_wiki_path(@project, wiki_page) do
     = wiki_page.title.capitalize
diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml
index fbe192a40ec5c877c3d712cfc608fdfd0e0c8f94..df0ec14eb3b6e769dcb13e287ae64d29d418f6d7 100644
--- a/app/views/projects/wikis/edit.html.haml
+++ b/app/views/projects/wikis/edit.html.haml
@@ -8,7 +8,7 @@
   .nav-text
     %h2.wiki-page-title
       - if @page.persisted?
-        = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page)
+        = link_to @page.title.capitalize, project_wiki_path(@project, @page)
       - else
         = @page.title.capitalize
       %span.light
@@ -23,10 +23,10 @@
       = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
         New page
     - if @page.persisted?
-      = link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn" do
+      = link_to project_wiki_history_path(@project, @page), class: "btn" do
         Page history
       - if can?(current_user, :admin_wiki, @project)
-        = link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-danger" do
+        = link_to project_wiki_path(@project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-danger" do
           Delete
 
 = render 'form'
diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml
index 0e47e2a5fa370d975e826c53206ffddf4fa62899..306feeff259016b09b02a09510d7033494f3fab6 100644
--- a/app/views/projects/wikis/history.html.haml
+++ b/app/views/projects/wikis/history.html.haml
@@ -6,7 +6,7 @@
 
   .nav-text
     %h2.wiki-page-title
-      = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page)
+      = link_to @page.title.capitalize, project_wiki_path(@project, @page)
       %span.light
         &middot;
         History
diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml
index 5fba2b1a5ae31e5b548e0a8510affcdef16537f4..dece1fad0bb75769904b20cbfebf0fe75d043643 100644
--- a/app/views/projects/wikis/pages.html.haml
+++ b/app/views/projects/wikis/pages.html.haml
@@ -9,7 +9,7 @@
         Wiki Pages
 
     .nav-controls
-      = link_to namespace_project_wikis_git_access_path(@project.namespace, @project), class: 'btn' do
+      = link_to project_wikis_git_access_path(@project), class: 'btn' do
         = icon('cloud-download')
         Clone repository
 
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index f003ff6b63fff987c5b6714b611db62f15713785..13591dd8e74c2d2883112b0673814188ae304b2b 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -22,7 +22,7 @@
 - if @page.historical?
   .warning_message
     This is an old version of this page.
-    You can view the #{link_to "most recent version", namespace_project_wiki_path(@project.namespace, @project, @page)} or browse the #{link_to "history", namespace_project_wiki_history_path(@project.namespace, @project, @page)}.
+    You can view the #{link_to "most recent version", project_wiki_path(@project, @page)} or browse the #{link_to "history", project_wiki_history_path(@project, @page)}.
 
 .wiki-holder.prepend-top-default.append-bottom-default
   .wiki
diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml
index 7f1f807e2e7724a0adf53e02d747fc3ecfb57aa5..de473c23d666213ba72da973a0071facb27c2e76 100644
--- a/app/views/search/results/_blob.html.haml
+++ b/app/views/search/results/_blob.html.haml
@@ -3,7 +3,7 @@
   .file-holder
     .js-file-title.file-title
       - ref = @search_results.repository_ref
-      - blob_link = namespace_project_blob_path(@project.namespace, @project, tree_join(ref, file_name))
+      - blob_link = project_blob_path(@project, tree_join(ref, file_name))
       = link_to blob_link do
         %i.fa.fa-file
         %strong
diff --git a/app/views/search/results/_snippet_title.html.haml b/app/views/search/results/_snippet_title.html.haml
index 026f404ce072dd1a1717a4e5925486fff1394283..aef825691e07c19bd6a17fdc35d12758133af01b 100644
--- a/app/views/search/results/_snippet_title.html.haml
+++ b/app/views/search/results/_snippet_title.html.haml
@@ -11,7 +11,7 @@
 
   %small.pull-right.cgray
     - if snippet_title.project_id?
-      = link_to snippet_title.project.name_with_namespace, namespace_project_path(snippet_title.project.namespace, snippet_title.project)
+      = link_to snippet_title.project.name_with_namespace, project_path(snippet_title.project)
 
   .snippet-info
     = snippet_title.to_reference
diff --git a/app/views/search/results/_wiki_blob.html.haml b/app/views/search/results/_wiki_blob.html.haml
index d87f9df26773f057a413b713ffc9ecfa0bb81225..16a0e432d62343f61bc605b033233c5268870628 100644
--- a/app/views/search/results/_wiki_blob.html.haml
+++ b/app/views/search/results/_wiki_blob.html.haml
@@ -2,7 +2,7 @@
 .blob-result
   .file-holder
     .js-file-title.file-title
-      = link_to namespace_project_wiki_path(@project.namespace, @project, wiki_blob.basename) do
+      = link_to project_wiki_path(@project, wiki_blob.basename) do
         %i.fa.fa-file
         %strong
           = wiki_blob.basename
diff --git a/app/views/shared/_commit_message_container.html.haml b/app/views/shared/_commit_message_container.html.haml
index 4b98ff8824178626f246325f55a2581289001a18..2329de9e11fd1f698f84891913bd5fe376e9cbfe 100644
--- a/app/views/shared/_commit_message_container.html.haml
+++ b/app/views/shared/_commit_message_container.html.haml
@@ -2,7 +2,7 @@
   - nonce = SecureRandom.hex
   - descriptions = local_assigns.slice(:message_with_description, :message_without_description)
   = label_tag "commit_message-#{nonce}", class: 'control-label' do
-    Commit message
+    #{ _('Commit message') }
   .col-sm-10
     .commit-message-container
       .max-width-marker
diff --git a/app/views/shared/_issuable_meta_data.html.haml b/app/views/shared/_issuable_meta_data.html.haml
index 1d4fd71522d4628d96ab2f65ba4cba0c5f61fb64..435acbc634cabd37d9e695d47d0ad036fead6bed 100644
--- a/app/views/shared/_issuable_meta_data.html.haml
+++ b/app/views/shared/_issuable_meta_data.html.haml
@@ -5,21 +5,21 @@
 - issuable_mr        = @issuable_meta_data[issuable.id].merge_requests_count
 
 - if issuable_mr > 0
-  %li
+  %li.issuable-mr.hidden-xs
     = image_tag('icon-merge-request-unmerged.svg', class: 'icon-merge-request-unmerged')
     = issuable_mr
 
 - if upvotes > 0
-  %li
+  %li.issuable-upvotes.hidden-xs
     = icon('thumbs-up')
     = upvotes
 
 - if downvotes > 0
-  %li
+  %li.issuable-downvotes.hidden-xs
     = icon('thumbs-down')
     = downvotes
 
-%li
+%li.issuable-comments.hidden-xs
   = link_to issuable_url, class: ('no-comments' if note_count.zero?) do
     = icon('comments')
     = note_count
diff --git a/app/views/shared/_issues.html.haml b/app/views/shared/_issues.html.haml
index 3a49227961f4ce30d9d9af5a6f24ac6b64dfc26e..49555b6ff4e464c015d3353e181a496aded78432 100644
--- a/app/views/shared/_issues.html.haml
+++ b/app/views/shared/_issues.html.haml
@@ -1,6 +1,6 @@
 - if @issues.to_a.any?
   .panel.panel-default.panel-small.panel-without-border
-    %ul.content-list.issues-list
+    %ul.content-list.issues-list.issuable-list
       = render partial: 'projects/issues/issue', collection: @issues
   = paginate @issues, theme: "gitlab"
 - else
diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml
index c185e9b73ee97bf89ed8bf70a2be46781f981aa8..2f776a17f45dccf64e6c5d08d592d50f52520611 100644
--- a/app/views/shared/_label.html.haml
+++ b/app/views/shared/_label.html.haml
@@ -1,12 +1,13 @@
 - label_css_id = dom_id(label)
 - status = label_subscription_status(label, @project).inquiry if current_user
 - subject = local_assigns[:subject]
+- toggle_subscription_path = toggle_subscription_label_path(label, @project) if current_user
 
 %li{ id: label_css_id, data: { id: label.id } }
   = render "shared/label_row", label: label
 
   .visible-xs.visible-sm-inline-block.visible-md-inline-block.dropdown
-    %button.btn.btn-default.label-options-toggle{ data: { toggle: "dropdown" } }
+    %button.btn.btn-default.label-options-toggle{ type: 'button', data: { toggle: "dropdown" } }
       Options
       = icon('caret-down')
     .dropdown-menu.dropdown-menu-align-right
@@ -17,18 +18,18 @@
         %li
           = link_to_label(label, subject: subject) do
             view open issues
-        - if current_user && defined?(@project)
+        - if current_user
           %li.label-subscription
-            - if label.is_a?(ProjectLabel)
-              %a.js-subscribe-button.label-subscribe-button{ role: 'button', href: '#', data: { status: status, url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } }
-                %span= label_subscription_toggle_button_text(label, @project)
-            - else
-              %a.js-unsubscribe-button.label-subscribe-button{ role: 'button', href: '#', class: ('hidden' if status.unsubscribed?), data: { url: group_label_unsubscribe_path(label, @project) } }
+            - if can_subscribe_to_label_in_different_levels?(label)
+              %a.js-unsubscribe-button.label-subscribe-button{ role: 'button', href: '#', class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_path } }
                 %span Unsubscribe
-              %a.js-subscribe-button.label-subscribe-button{ role: 'button', href: '#', class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } }
+              %a.js-subscribe-button.label-subscribe-button{ role: 'button', href: '#', class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_project_label_path(@project, label) } }
                 %span Subscribe at project level
               %a.js-subscribe-button.label-subscribe-button{ role: 'button', href: '#', class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_group_label_path(label.group, label) } }
                 %span Subscribe at group level
+            - else
+              %a.js-subscribe-button.label-subscribe-button{ role: 'button', href: '#', data: { status: status, url: toggle_subscription_path } }
+                %span= label_subscription_toggle_button_text(label, @project)
 
         - if can?(current_user, :admin_label, label)
           %li
@@ -42,14 +43,10 @@
     = link_to_label(label, subject: subject, css_class: 'btn btn-transparent btn-action') do
       view open issues
 
-    - if current_user && defined?(@project)
+    - if current_user
       .label-subscription.inline
-        - if label.is_a?(ProjectLabel)
-          %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ type: 'button', data: { status: status, url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } }
-            %span= label_subscription_toggle_button_text(label, @project)
-            = icon('spinner spin', class: 'label-subscribe-button-loading')
-        - else
-          %button.js-unsubscribe-button.label-subscribe-button.btn.btn-default{ type: 'button', class: ('hidden' if status.unsubscribed?), data: { url: group_label_unsubscribe_path(label, @project) } }
+        - if can_subscribe_to_label_in_different_levels?(label)
+          %button.js-unsubscribe-button.label-subscribe-button.btn.btn-default{ type: 'button', class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_path } }
             %span Unsubscribe
             = icon('spinner spin', class: 'label-subscribe-button-loading')
 
@@ -59,13 +56,17 @@
               = icon('chevron-down')
             %ul.dropdown-menu
               %li
-                %a.js-subscribe-button{ data: { url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } }
+                %a.js-subscribe-button{ class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_project_label_path(@project, label) } }
                   Project level
-                %a.js-subscribe-button{ data: { url: toggle_subscription_group_label_path(label.group, label) } }
+                %a.js-subscribe-button{ class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_group_label_path(label.group, label) } }
                   Group level
+        - else
+          %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ type: 'button', data: { status: status, url: toggle_subscription_path } }
+            %span= label_subscription_toggle_button_text(label, @project)
+            = icon('spinner spin', class: 'label-subscribe-button-loading')
 
     - if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_label, 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
+      = link_to promote_project_label_path(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)
@@ -76,10 +77,10 @@
         %span.sr-only Delete
         = icon('trash-o')
 
-  - if current_user && defined?(@project)
-    - if label.is_a?(ProjectLabel)
+  - if current_user
+    - if can_subscribe_to_label_in_different_levels?(label)
       :javascript
-        new gl.ProjectLabelSubscription('##{dom_id(label)} .label-subscription');
+        new gl.GroupLabelSubscription('##{dom_id(label)} .label-subscription');
     - else
       :javascript
-        new gl.GroupLabelSubscription('##{dom_id(label)} .label-subscription');
+        new gl.ProjectLabelSubscription('##{dom_id(label)} .label-subscription');
diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml
index d28f9421ecf6c2bfc77271ef8af21b8eafd06404..7f58298c60fc70ab7c2f85fffa615c534d844b5b 100644
--- a/app/views/shared/_label_row.html.haml
+++ b/app/views/shared/_label_row.html.haml
@@ -2,11 +2,11 @@
   - if can?(current_user, :admin_label, @project)
     .draggable-handler
       = icon('bars')
-    .js-toggle-priority.toggle-priority{ data: { url: remove_priority_namespace_project_label_path(@project.namespace, @project, label),
+    .js-toggle-priority.toggle-priority{ data: { url: remove_priority_project_label_path(@project, label),
       dom_id: dom_id(label), type: label.type } }
-      %button.add-priority.btn.has-tooltip{ title: 'Prioritize', :'data-placement' => 'top' }
+      %button.add-priority.btn.has-tooltip{ title: 'Prioritize', type: 'button', :'data-placement' => 'top' }
         = icon('star-o')
-      %button.remove-priority.btn.has-tooltip{ title: 'Remove priority', :'data-placement' => 'top' }
+      %button.remove-priority.btn.has-tooltip{ title: 'Remove priority', type: 'button', :'data-placement' => 'top' }
         = icon('star')
   %span.label-name
     = link_to_label(label, subject: @project, tooltip: false)
diff --git a/app/views/shared/_merge_requests.html.haml b/app/views/shared/_merge_requests.html.haml
index eecbb32e90e3e0856b30d263be0460953297bd4b..0517896cfbd7ece477764e29ef2e311bf8da0b22 100644
--- a/app/views/shared/_merge_requests.html.haml
+++ b/app/views/shared/_merge_requests.html.haml
@@ -1,6 +1,6 @@
 - if @merge_requests.to_a.any?
   .panel.panel-default.panel-small.panel-without-border
-    %ul.content-list.mr-list
+    %ul.content-list.mr-list.issuable-list
       = render partial: 'projects/merge_requests/merge_request', collection: @merge_requests
 
   = paginate @merge_requests, theme: "gitlab"
diff --git a/app/views/shared/_mini_pipeline_graph.html.haml b/app/views/shared/_mini_pipeline_graph.html.haml
index aa93572bf9437a1f27baee17954f1ef8628d73bc..dff847159d3c5d973a3e1aff6ad275fa0c7020b4 100644
--- a/app/views/shared/_mini_pipeline_graph.html.haml
+++ b/app/views/shared/_mini_pipeline_graph.html.haml
@@ -6,7 +6,7 @@
       - status_klass = "ci-status-icon ci-status-icon-#{detailed_status.group}"
 
       .stage-container.dropdown{ class: klass }
-        %button.mini-pipeline-graph-dropdown-toggle.has-tooltip.js-builds-dropdown-button{ class: "ci-status-icon-#{detailed_status.group}", type: 'button', data: { toggle: 'dropdown', title: "#{stage.name}: #{detailed_status.label}", placement: 'top', "stage-endpoint" => stage_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline, stage: stage.name) } }
+        %button.mini-pipeline-graph-dropdown-toggle.has-tooltip.js-builds-dropdown-button{ class: "ci-status-icon-#{detailed_status.group}", type: 'button', data: { toggle: 'dropdown', title: "#{stage.name}: #{detailed_status.label}", placement: 'top', "stage-endpoint" => stage_project_pipeline_path(pipeline.project, pipeline, stage: stage.name) } }
           = custom_icon(icon_status)
           = icon('caret-down')
 
diff --git a/app/views/shared/_new_commit_form.html.haml b/app/views/shared/_new_commit_form.html.haml
index 25a56f84ec5f4971a0d726a7fc254741f444bb40..0a4a24ae8075a772679784d9312acbe7cd17893d 100644
--- a/app/views/shared/_new_commit_form.html.haml
+++ b/app/views/shared/_new_commit_form.html.haml
@@ -5,16 +5,12 @@
 - else
   - if can?(current_user, :push_code, @project)
     .form-group.branch
-      = label_tag 'branch_name', 'Target branch', class: 'control-label'
+      = label_tag 'branch_name', _('Target Branch'), class: 'control-label'
       .col-sm-10
         = text_field_tag 'branch_name', @branch_name || tree_edit_branch, required: true, class: "form-control js-branch-name ref-name"
 
         .js-create-merge-request-container
-          .checkbox
-            - nonce = SecureRandom.hex
-            = label_tag "create_merge_request-#{nonce}" do
-              = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: "create_merge_request-#{nonce}"
-              Start a <strong>new merge request</strong> with these changes
+          = render 'shared/new_merge_request_checkbox'
   - else
     = hidden_field_tag 'branch_name', @branch_name || tree_edit_branch
     = hidden_field_tag 'create_merge_request', 1
diff --git a/app/views/shared/_new_merge_request_checkbox.html.haml b/app/views/shared/_new_merge_request_checkbox.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..133c31f09c4487a5bbf47ea3a9d97521ad36aec0
--- /dev/null
+++ b/app/views/shared/_new_merge_request_checkbox.html.haml
@@ -0,0 +1,8 @@
+.checkbox
+  - nonce = SecureRandom.hex
+  = label_tag "create_merge_request-#{nonce}" do
+    = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: "create_merge_request-#{nonce}"
+    - translation_variables = { new_merge_request: "<strong>#{_('new merge request')}</strong>" }
+    - translation = _('Start a %{new_merge_request} with these changes') % translation_variables
+    #{ translation.html_safe }
+
diff --git a/app/views/shared/_no_password.html.haml b/app/views/shared/_no_password.html.haml
index b561e6dc2480b2610365d3f6873124683fe4e757..9b1a467df6b00e94f344604492f5f9983bc65685 100644
--- a/app/views/shared/_no_password.html.haml
+++ b/app/views/shared/_no_password.html.haml
@@ -1,9 +1,8 @@
-- if cookies[:hide_no_password_message].blank? && !current_user.hide_no_password && current_user.require_password?
+- if show_no_password_message?
   .no-password-message.alert.alert-warning
-    - set_password_link = link_to s_('SetPasswordToCloneLink|set a password'), edit_profile_password_path
-    - translation_params = { protocol: gitlab_config.protocol.upcase, set_password_link: set_password_link }
+    - translation_params = { protocol: gitlab_config.protocol.upcase, set_password_link: link_to_set_password }
     - set_password_message = _("You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account") % translation_params
-
+    = set_password_message.html_safe
     .alert-link-group
       = link_to _("Don't show again"), profile_path(user: {hide_no_password: true}), method: :put
       |
diff --git a/app/views/shared/_no_ssh.html.haml b/app/views/shared/_no_ssh.html.haml
index e7815e28017807bb87f7a1d6c6de9eaa26534d83..17ef5327341de20fd1eadd573e2b0edabea04e45 100644
--- a/app/views/shared/_no_ssh.html.haml
+++ b/app/views/shared/_no_ssh.html.haml
@@ -1,8 +1,8 @@
-- if cookies[:hide_no_ssh_message].blank? && !current_user.hide_no_ssh_key && current_user.require_ssh_key?
+- if show_no_ssh_key_message?
   .no-ssh-key-message.alert.alert-warning
     - add_ssh_key_link = link_to s_('MissingSSHKeyWarningLink|add an SSH key'), profile_keys_path, class: 'alert-link'
     - ssh_message = _("You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile") % { add_ssh_key_link: add_ssh_key_link }
-    #{ ssh_message.html_safe }
+    = ssh_message.html_safe
     .alert-link-group
       = link_to _("Don't show again"), profile_path(user: {hide_no_ssh_key: true}), method: :put, class: 'alert-link'
       |
diff --git a/app/views/shared/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml
index d52bb6b4dd792bf953aa7ddabe84e692d39e6a9b..4498c8f83493810b9c666d72db46daa93b315077 100644
--- a/app/views/shared/_ref_switcher.html.haml
+++ b/app/views/shared/_ref_switcher.html.haml
@@ -1,12 +1,12 @@
 - dropdown_toggle_text = @ref || @project.default_branch
-= form_tag switch_namespace_project_refs_path(@project.namespace, @project), method: :get, class: "project-refs-form" do
+= form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do
   = hidden_field_tag :destination, destination
   - if defined?(path)
     = hidden_field_tag :path, path
   - @options && @options.each do |key, value|
     = hidden_field_tag key, value, id: nil
   .dropdown
-    = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_namespace_project_path(@project.namespace, @project), field_name: 'ref', submit_form_on_click: true }, { toggle_class: "js-project-refs-dropdown" }
+    = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_project_path(@project), field_name: 'ref', submit_form_on_click: true }, { toggle_class: "js-project-refs-dropdown" }
     .dropdown-menu.dropdown-menu-selectable.git-revision-dropdown{ class: ("dropdown-menu-align-right" if local_assigns[:align_right]) }
       = dropdown_title _("Switch branch/tag")
       = dropdown_filter _("Search branches and tags")
diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml
index a212c714826d4bc2160e85e2ea0337a9bc2bbeeb..785a500e44ed0c334d053bcc14d7039c3d2e732a 100644
--- a/app/views/shared/_sort_dropdown.html.haml
+++ b/app/views/shared/_sort_dropdown.html.haml
@@ -1,3 +1,5 @@
+- viewing_issues = controller.controller_name == 'issues' || controller.action_name == 'issues'
+
 .dropdown.inline.prepend-left-10
   %button.dropdown-toggle{ type: 'button', data: {toggle: 'dropdown' } }
     - if @sort.present?
@@ -23,7 +25,7 @@
         = sort_title_milestone_soon
       = link_to page_filter_path(sort: sort_value_milestone_later, label: true) do
         = sort_title_milestone_later
-      - if controller.controller_name == 'issues' || controller.action_name == 'issues'
+      - if viewing_issues
         = link_to page_filter_path(sort: sort_value_due_date_soon, label: true) do
           = sort_title_due_date_soon
         = link_to page_filter_path(sort: sort_value_due_date_later, label: true) do
diff --git a/app/views/shared/empty_states/_labels.html.haml b/app/views/shared/empty_states/_labels.html.haml
index 5e2f4cf109ded6a4d8cf0a1fc968a79a16b29e67..bfda522f2f66f397df3379c2bf63dfb42b1644de 100644
--- a/app/views/shared/empty_states/_labels.html.haml
+++ b/app/views/shared/empty_states/_labels.html.haml
@@ -7,5 +7,5 @@
       %h4 Labels can be applied to issues and merge requests to categorize them.
       %p You can also star a 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'
+        = link_to 'New label', new_project_label_path(@project), class: 'btn btn-new', title: 'New label', id: 'new_label_link'
+        = link_to 'Generate a default set of labels', generate_project_labels_path(@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/form_elements/_description.html.haml b/app/views/shared/form_elements/_description.html.haml
index 307d4919224e1ae0fd5cbebf939de7f39ebd9f61..f65bb6a29e6072031a2f84cb78fc4c61244db969 100644
--- a/app/views/shared/form_elements/_description.html.haml
+++ b/app/views/shared/form_elements/_description.html.haml
@@ -2,10 +2,10 @@
 - model = local_assigns.fetch(:model)
 
 - form = local_assigns.fetch(:form)
-- supports_slash_commands = model.new_record?
+- supports_quick_actions = model.new_record?
 
-- if supports_slash_commands
-  - preview_url = preview_markdown_path(project, slash_commands_target_type: model.class.name)
+- if supports_quick_actions
+  - preview_url = preview_markdown_path(project, quick_actions_target_type: model.class.name)
 - else
   - preview_url = preview_markdown_path(project)
 
@@ -17,7 +17,7 @@
       = render 'projects/zen', f: form, attr: :description,
                                classes: 'note-textarea',
                                placeholder: "Write a comment or drag your files here...",
-                               supports_slash_commands: supports_slash_commands
-      = render 'shared/notes/hints', supports_slash_commands: supports_slash_commands
+                               supports_quick_actions: supports_quick_actions
+      = render 'shared/notes/hints', supports_quick_actions: supports_quick_actions
       .clearfix
       .error-alert
diff --git a/app/views/shared/icons/_icon_empty_metrics.svg b/app/views/shared/icons/_icon_empty_metrics.svg
new file mode 100644
index 0000000000000000000000000000000000000000..24fa353f3ba5b021c39ffb9c5a34d03424997868
--- /dev/null
+++ b/app/views/shared/icons/_icon_empty_metrics.svg
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">
+  <g fill="#E5E5E5">
+    <path d="M32 64C30.8954305 64 30 63.1045695 30 62 30 60.8954305 30.8954305 60 32 60 33.8894444 60 35.7536611 59.8131396 37.574335 59.4454933 38.6570511 59.2268618 39.7120017 59.9273408 39.9306331 61.0100569 40.1492646 62.0927729 39.4487856 63.1477235 38.3660695 63.366355 36.285133 63.7865558 34.1557023 64 32 64zM49.2301062 58.9696428C51.0302775 57.8173242 52.7114504 56.4871355 54.247711 55.0008916 55.0415758 54.232873 55.0625283 52.9667164 54.2945097 52.1728516 53.5264912 51.3789869 52.2603346 51.3580344 51.4664698 52.1260529 50.1212672 53.4274592 48.6493395 54.5920875 47.0736141 55.6007347 46.1433158 56.1962335 45.8719072 57.4331365 46.4674061 58.3634348 47.0629049 59.2937331 48.2998079 59.5651416 49.2301062 58.9696428zM61.0426034 45.4531856C61.9412068 43.5163476 62.6441937 41.4911051 63.1388045 39.4034279 63.393449 38.3286117 62.7285685 37.2508708 61.6537523 36.9962262 60.5789361 36.7415816 59.5011952 37.4064621 59.2465506 38.4812784 58.8141946 40.3061875 58.1997219 42.0764286 57.4141077 43.7697311 56.9492346 44.7717126 57.3846469 45.9608331 58.3866284 46.4257062 59.3886098 46.8905793 60.5777303 46.455167 61.0426034 45.4531856zM63.7270657 27.8034151C63.4476841 25.6718707 62.9558906 23.5863203 62.2616468 21.5714028 61.9018246 20.527084 60.7635435 19.9721898 59.7192246 20.3320119 58.6749058 20.6918341 58.1200116 21.8301152 58.4798337 22.874434 59.0867105 24.6357842 59.5166381 26.45898 59.760988 28.3232492 59.9045362 29.4184513 60.9087418 30.1899192 62.0039439 30.046371 63.099146 29.9028228 63.8706139 28.8986173 63.7270657 27.8034151zM56.4699838 11.3781121C55.0919588 9.74451505 53.5537382 8.25140603 51.8798083 6.92273835 51.0146495 6.23602588 49.7566092 6.38068523 49.0698968 7.24584403 48.3831843 8.11100284 48.5278436 9.36904308 49.3930024 10.0557555 50.8587525 11.2191822 52.2058153 12.5267396 53.4125204 13.9572433 54.1247279 14.8015385 55.3865225 14.9086168 56.2308177 14.1964094 57.0751129 13.484202 57.1821912 12.2224073 56.4699838 11.3781121zM41.481294 1.42849704C39.4470333.798260231 37.3474846.371987025 35.2067823.158824109 34.1076485.0493765922 33.1278998.851675811 33.0184523 1.95080957 32.9090048 3.04994333 33.711304 4.02969203 34.8104377 4.13913955 36.6833634 4.32563829 38.5191483 4.69835932 40.297557 5.24933028 41.3526509 5.57621023 42.4729622 4.98587613 42.7998421 3.93078217 43.1267221 2.8756882 42.536388 1.75537699 41.481294 1.42849704zM23.6558195 1.0993008C21.5852929 1.6571259 19.5822296 2.42161363 17.6728876 3.37914679 16.6855233 3.874309 16.2865147 5.07613416 16.7816769 6.06349841 17.2768392 7.05086266 18.4786643 7.44987125 19.4660286 6.95470905 21.1354949 6.11747332 22.8864813 5.44919307 24.6963667 4.96158787 25.7629079 4.67424869 26.3945759 3.57671185 26.1072367 2.51017072 25.8198975 1.44362959 24.7223606.811961615 23.6558195 1.0993008zM8.36290105 10.4291871C6.92120358 12.00815 5.63985273 13.7275139 4.53998784 15.5610549 3.97179016 16.5082746 4.27904822 17.7367631 5.22626792 18.3049608 6.17348763 18.8731585 7.40197615 18.5659004 7.97017383 17.6186807 8.9327668 16.0139803 10.054503 14.5087932 11.3168098 13.126301 12.0615972 12.3106016 12.0041117 11.0455771 11.1884123 10.3007897 10.372713 9.55600224 9.10768848 9.61348772 8.36290105 10.4291871zM.450120287 26.6230259C.151304663 28.3883054 0 30.1850053 0 32 0 32.2974081.00406268322 32.594367.0121750297 32.8908218.0423897377 33.994978.96197903 34.8655796 2.0661352 34.8353649 3.17029137 34.8051502 4.04089294 33.8855609 4.01067824 32.7814047 4.00356366 32.521412 4 32.2609289 4 32 4 30.4089462 4.13249902 28.8355581 4.39401589 27.2906242 4.57836807 26.2015475 3.84494393 25.1692294 2.75586724 24.9848772 1.66679054 24.800525.634472466 25.5339492.450120287 26.6230259zM2.45830096 44.3202494C3.28286321 46.2952494 4.30407075 48.1806071 5.50459135 49.9494734 6.124886 50.8634254 7.36863868 51.1014818 8.28259072 50.4811871 9.19654276 49.8608925 9.43459912 48.6171398 8.81430448 47.7031878 7.76386025 46.1554464 6.87058107 44.5062706 6.14951581 42.7791677 5.72395784 41.7598668 4.55266835 41.2785432 3.53336751 41.7041011 2.51406668 42.1296591 2.03274299 43.3009486 2.45830096 44.3202494zM13.73374 58.2776222C15.4883094 59.4994144 17.3614388 60.5433005 19.3262717 61.39161 20.3403619 61.8294398 21.5173756 61.3622885 21.9552054 60.3481983 22.3930351 59.3341082 21.9258838 58.1570945 20.9117937 57.7192647 19.1934726 56.9773858 17.5548741 56.0642026 16.0195384 54.9950736 15.1130877 54.3638678 13.8665707 54.5869979 13.2353649 55.4934487 12.6041591 56.3998995 12.8272892 57.6464164 13.73374 58.2776222zM30.6955071 63.9738646C29.5918263 63.9295649 28.7330282 62.9989428 28.7773279 61.895262 28.8216276 60.7915812 29.7522497 59.9327832 30.8559305 59.9770829 31.2344492 59.9922759 31.6140624 59.9999282 31.9946308 59.9999995 33.0992003 60.0002065 33.994463 60.8958047 33.994256 62.0003742 33.9940491 63.1049437 33.0984508 64.0002064 31.9938814 63.9999994 31.5600677 63.9999181 31.1272192 63.9911927 30.6955071 63.9738646zM30.1721098 44.2840559C30.7941711 46.023825 33.2407935 46.0619159 33.9167124 44.3423547L38.9452693 31.5495297 41.1315797 35.2685507C41.4908522 35.8796908 42.1468005 36.2549751 42.8557214 36.2549751L51.1106965 36.2549751C52.215266 36.2549751 53.1106965 35.3595446 53.1106965 34.2549751 53.1106965 33.1504056 52.215266 32.2549751 51.1106965 32.2549751L43.9999712 32.2549751 40.3112064 25.9802055C39.465988 24.5424477 37.3358287 24.7099356 36.7257006 26.2621229L32.1439734 37.9181973 26.2115967 21.3266406C25.5807315 19.562249 23.0875908 19.5563214 22.4483429 21.3176933L18.4775633 32.2587065 13 32.2587065C11.8954305 32.2587065 11 33.154137 11 34.2587065 11 35.363276 11.8954305 36.2587065 13 36.2587065L19.8793532 36.2587065C20.720826 36.2587065 21.4722973 35.732004 21.7593685 34.9410132L24.314328 27.9011249 30.1721098 44.2840559z"/>
+  </g>
+</svg>
diff --git a/app/views/shared/issuable/_bulk_update_sidebar.html.haml b/app/views/shared/issuable/_bulk_update_sidebar.html.haml
index a8a6d84128d83e945e40739e018dc1e1cf7f8af0..964fe5220f73c7cc5723c427282565cbdfdde583 100644
--- a/app/views/shared/issuable/_bulk_update_sidebar.html.haml
+++ b/app/views/shared/issuable/_bulk_update_sidebar.html.haml
@@ -1,7 +1,7 @@
 - type = local_assigns.fetch(:type)
 
 %aside.issues-bulk-update.js-right-sidebar.right-sidebar.affix-top{ data: { "offset-top" => "50", "spy" => "affix" }, "aria-live" => "polite" }
-  .issuable-sidebar
+  .issuable-sidebar.hidden
     = form_tag [:bulk_update, @project.namespace.becomes(Namespace), @project, type], method: :post, class: "bulk-update"  do
       .block
         .filter-item.inline.update-issues-btn.pull-left
@@ -31,7 +31,7 @@
         .title
           Milestone
         .filter-item
-          = dropdown_tag("Select milestone", options: { title: "Assign milestone", toggle_class: "js-milestone-select js-extra-options js-filter-submit js-filter-bulk-update", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", placeholder: "Search milestones", data: { show_no: true, field_name: "update[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), use_id: true, default_label: "Milestone" } })
+          = dropdown_tag("Select milestone", options: { title: "Assign milestone", toggle_class: "js-milestone-select js-extra-options js-filter-submit js-filter-bulk-update", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", placeholder: "Search milestones", data: { show_no: true, field_name: "update[milestone_id]", project_id: @project.id, milestones: project_milestones_path(@project, :json), use_id: true, default_label: "Milestone" } })
       .block
         .title
           Labels
diff --git a/app/views/shared/issuable/_label_page_default.html.haml b/app/views/shared/issuable/_label_page_default.html.haml
index 9a8529c6cbb7a90ced227ec5b4c3fd5e50c9b2e8..e8feff32d2642470c203764fed2fc01aa1fadafe 100644
--- a/app/views/shared/issuable/_label_page_default.html.haml
+++ b/app/views/shared/issuable/_label_page_default.html.haml
@@ -20,7 +20,7 @@
             %a.dropdown-toggle-page{ href: "#" }
               Create new label
         %li
-          = link_to namespace_project_labels_path(@project.namespace, @project), :"data-is-link" => true do
+          = link_to project_labels_path(@project), :"data-is-link" => true do
             - if show_create && @project && can?(current_user, :admin_label, @project)
               Manage labels
             - else
diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml
index 6750921338a6eb5f3f52f79b14266beb48c29541..955b8866c2c277aa1030a65b9cbca3adadcbd002 100644
--- a/app/views/shared/issuable/_milestone_dropdown.html.haml
+++ b/app/views/shared/issuable/_milestone_dropdown.html.haml
@@ -11,10 +11,10 @@
     %ul.dropdown-footer-list
       - if can? current_user, :admin_milestone, project
         %li
-          = link_to new_namespace_project_milestone_path(project.namespace, project), title: "New Milestone" do
+          = link_to new_project_milestone_path(project), title: "New Milestone" do
             Create new
       %li
-        = link_to namespace_project_milestones_path(project.namespace, project) do
+        = link_to project_milestones_path(project) do
           - if can? current_user, :admin_milestone, project
             Manage milestones
           - else
diff --git a/app/views/shared/issuable/_nav.html.haml b/app/views/shared/issuable/_nav.html.haml
index cf7ba52d840c3cd7b40df84d09f792fab8469897..3f03cc7a275a2a17437fba46bbb650889814417b 100644
--- a/app/views/shared/issuable/_nav.html.haml
+++ b/app/views/shared/issuable/_nav.html.haml
@@ -1,24 +1,25 @@
 - type = local_assigns.fetch(:type, :issues)
 - page_context_word = type.to_s.humanize(capitalize: false)
 - issuables = @issues || @merge_requests
-- closed_title = 'Filter by issues that are currently closed.'
 
 %ul.nav-links.issues-state-filters
   %li{ class: active_when(params[:state] == 'opened') }>
-    %button.btn.btn-link{ id: 'state-opened', title: "Filter by #{page_context_word} that are currently opened.", type: 'button', data: { state: 'opened' } }
+    = link_to page_filter_path(state: 'opened', label: true), id: 'state-opened', title: "Filter by #{page_context_word} that are currently opened.", data: { state: 'opened' } do
       #{issuables_state_counter_text(type, :opened)}
 
   - if type == :merge_requests
     %li{ class: active_when(params[:state] == 'merged') }>
-      %button.btn.btn-link{ id: 'state-merged', title: 'Filter by merge requests that are currently merged.', type: 'button', data: { state: 'merged' } }
+      = link_to page_filter_path(state: 'merged', label: true), id: 'state-merged', title: 'Filter by merge requests that are currently merged.', data: { state: 'merged' } do
         #{issuables_state_counter_text(type, :merged)}
 
-    - closed_title = 'Filter by merge requests that are currently closed and unmerged.'
-
-  %li{ class: active_when(params[:state] == 'closed') }>
-    %button.btn.btn-link{ id: 'state-closed', title: closed_title, type: 'button', data: { state: 'closed' } }
-      #{issuables_state_counter_text(type, :closed)}
+    %li{ class: active_when(params[:state] == 'closed') }>
+      = link_to page_filter_path(state: 'closed', label: true), id: 'state-closed', title: 'Filter by merge requests that are currently closed and unmerged.', data: { state: 'closed' } do
+        #{issuables_state_counter_text(type, :closed)}
+  - else
+    %li{ class: active_when(params[:state] == 'closed') }>
+      = link_to page_filter_path(state: 'closed', label: true), id: 'state-closed', title: 'Filter by issues that are currently closed.', data: { state: 'closed' } do
+        #{issuables_state_counter_text(type, :closed)}
 
   %li{ class: active_when(params[:state] == 'all') }>
-    %button.btn.btn-link{ id: 'state-all', title: "Show all #{page_context_word}.", type: 'button', data: { state: 'all' } }
+    = link_to page_filter_path(state: 'all', label: true), id: 'state-all', title: "Show all #{page_context_word}.", data: { state: 'all' } do
       #{issuables_state_counter_text(type, :all)}
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index d3d290692a21dec759fb50337f5c9323e5ebe7a1..ae8905672256b89f41704e5e038d1dad53021cac 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -23,7 +23,7 @@
             .scroll-container
               %ul.tokens-container.list-unstyled
                 %li.input-token
-                  %input.form-control.filtered-search{ id: "filtered-search-#{type.to_s}", placeholder: 'Search or filter results...', data: { 'project-id' => @project.id, 'username-params' => @users.to_json(only: [:id, :username]), 'base-endpoint' => namespace_project_path(@project.namespace, @project) } }
+                  %input.form-control.filtered-search{ search_filter_input_options(type) }
               = icon('filter')
             #js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
               %ul{ data: { dropdown: true } }
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index e49bd5ebb13c8c7e4b90cdab4a4042961c1c0123..ecbaa9017924d5bd00904d83f17c18d94b683119 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -3,7 +3,7 @@
   = page_specific_javascript_bundle_tag('common_vue')
   = page_specific_javascript_bundle_tag('sidebar')
 
-%aside.right-sidebar.js-right-sidebar{ data: { "offset-top" => "50", "spy" => "affix" }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' }
+%aside.right-sidebar.js-right-sidebar{ data: { "offset-top" => "50", "spy" => "affix", signed: { in: current_user.present? } }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' }
   .issuable-sidebar{ data: { endpoint: "#{issuable_json_path(issuable)}" } }
     - can_edit_issuable = can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
     .block.issuable-sidebar-header
@@ -20,7 +20,7 @@
         .block.todo.hide-expanded
           = render "shared/issuable/sidebar_todo", todo: todo, issuable: issuable, is_collapsed: true
       .block.assignee
-        = render "shared/issuable/sidebar_assignees", issuable: issuable, can_edit_issuable: can_edit_issuable
+        = render "shared/issuable/sidebar_assignees", issuable: issuable, can_edit_issuable: can_edit_issuable, signed_in: current_user.present?
       .block.milestone
         .sidebar-collapsed-icon
           = icon('clock-o', 'aria-hidden': 'true')
@@ -37,13 +37,13 @@
             = link_to 'Edit', '#', class: 'edit-link pull-right'
         .value.hide-collapsed
           - if issuable.milestone
-            = link_to issuable.milestone.title, namespace_project_milestone_path(@project.namespace, @project, issuable.milestone), class: "bold has-tooltip", title: milestone_remaining_days(issuable.milestone), data: { container: "body", html: 1 }
+            = link_to issuable.milestone.title, project_milestone_path(@project, issuable.milestone), class: "bold has-tooltip", title: milestone_remaining_days(issuable.milestone), data: { container: "body", html: 1 }
           - else
             %span.no-value None
 
         .selectbox.hide-collapsed
           = f.hidden_field 'milestone_id', value: issuable.milestone_id, id: nil
-          = dropdown_tag('Milestone', options: { title: 'Assign milestone', toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: 'Search milestones', data: { show_no: true, field_name: "#{issuable.to_ability_name}[milestone_id]", project_id: @project.id, issuable_id: issuable.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable), use_id: true, default_no: true, selected: (issuable.milestone.name if issuable.milestone), null_default: true }})
+          = dropdown_tag('Milestone', options: { title: 'Assign milestone', toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: 'Search milestones', data: { show_no: true, field_name: "#{issuable.to_ability_name}[milestone_id]", project_id: @project.id, issuable_id: issuable.id, milestones: project_milestones_path(@project, :json), ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable), use_id: true, default_no: true, selected: (issuable.milestone.name if issuable.milestone), null_default: true }})
       - if issuable.has_attribute?(:time_estimate)
         #issuable-time-tracker.block
           // Fallback while content is loading
@@ -106,7 +106,7 @@
             - selected_labels.each do |label|
               = hidden_field_tag "#{issuable.to_ability_name}[label_names][]", label.id, id: nil
             .dropdown
-              %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{ type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project) } }
+              %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{ type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path), issue_update: issuable_json_path(issuable), labels: (project_labels_path(@project, :json) if @project) } }
                 %span.dropdown-toggle-text{ class: ("is-default" if selected_labels.empty?) }
                   = multi_label_name(selected_labels, "Labels")
                 = icon('chevron-down', 'aria-hidden': 'true')
diff --git a/app/views/shared/issuable/_sidebar_assignees.html.haml b/app/views/shared/issuable/_sidebar_assignees.html.haml
index bcfa1dc826e6bc2b02ee68b6c8dda48cf62bc06f..57392cd7fbb2122629bfe9b01e17291b5f19b4a5 100644
--- a/app/views/shared/issuable/_sidebar_assignees.html.haml
+++ b/app/views/shared/issuable/_sidebar_assignees.html.haml
@@ -1,5 +1,5 @@
 - if issuable.is_a?(Issue)
-  #js-vue-sidebar-assignees{ data: { field: "#{issuable.to_ability_name}[assignee_ids]" } }
+  #js-vue-sidebar-assignees{ data: { field: "#{issuable.to_ability_name}[assignee_ids]", signed_in: signed_in } }
     .title.hide-collapsed
       Assignee
       = icon('spinner spin')
@@ -14,6 +14,9 @@
     = icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true')
     - if can_edit_issuable
       = link_to 'Edit', '#', class: 'edit-link pull-right'
+    - if !signed_in
+      %a.gutter-toggle.pull-right.js-sidebar-toggle{ role: "button", href: "#", "aria-label" => "Toggle sidebar" }
+        = sidebar_gutter_toggle_icon
   .value.hide-collapsed
     - if issuable.assignee
       = link_to_member(@project, issuable.assignee, size: 32, extra_class: 'bold') do
@@ -34,19 +37,20 @@
   - issuable.assignees.each do |assignee|
     = hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", assignee.id, id: nil, data: { avatar_url: assignee.avatar_url, name: assignee.name, username: assignee.username }
 
-  - options = { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), author_id: issuable.author_id, field_name: "#{issuable.to_ability_name}[assignee_ids][]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } }
-
+  - options = { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: current_user&.username, current_user: true, project_id: @project&.id, author_id: issuable.author_id, field_name: "#{issuable.to_ability_name}[assignee_ids][]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } }
   - title = 'Select assignee'
 
   - if issuable.is_a?(Issue)
     - unless issuable.assignees.any?
       = hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil
+    - dropdown_options = issue_assignees_dropdown_options
+    - title = dropdown_options[:title]
     - options[:toggle_class] += ' js-multiselect js-save-user-data'
     - data = { field_name: "#{issuable.to_ability_name}[assignee_ids][]" }
     - data[:multi_select] = true
     - data['dropdown-title'] = title
-    - data['dropdown-header'] = 'Assignee'
-    - data['max-select'] = 1
+    - data['dropdown-header'] = dropdown_options[:data][:'dropdown-header']
+    - data['max-select'] = dropdown_options[:data][:'max-select']
     - options[:data].merge!(data)
 
   = dropdown_tag(title, options: options)
diff --git a/app/views/shared/issuable/form/_merge_params.html.haml b/app/views/shared/issuable/form/_merge_params.html.haml
index bfa91629e1e432d8631fb71c41e2a4d4686a3586..8f6509a8ce863e661a1d680cc9e75f2494295290 100644
--- a/app/views/shared/issuable/form/_merge_params.html.haml
+++ b/app/views/shared/issuable/form/_merge_params.html.haml
@@ -11,8 +11,7 @@
   .col-sm-10.col-sm-offset-2
     - if issuable.can_remove_source_branch?(current_user)
       .checkbox
-        - initial_checkbox_value = issuable.merge_params.key?('force_remove_source_branch') ? issuable.force_remove_source_branch? : true
         = 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', initial_checkbox_value
+          = 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/_metadata_issue_assignee.html.haml b/app/views/shared/issuable/form/_metadata_issue_assignee.html.haml
index 77175c839a60bfb4d457f8fa058a30a3aaab99dd..567cde764e26bd0cd033ba6828357d926bc02684 100644
--- a/app/views/shared/issuable/form/_metadata_issue_assignee.html.haml
+++ b/app/views/shared/issuable/form/_metadata_issue_assignee.html.haml
@@ -7,5 +7,5 @@
     - if issuable.assignees.length === 0
       = hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil, data: { meta: '' }
 
-    = dropdown_tag(users_dropdown_label(issuable.assignees), options: issue_dropdown_options(issuable,false))
+    = dropdown_tag(users_dropdown_label(issuable.assignees), options: issue_assignees_dropdown_options)
   = link_to 'Assign to me', '#', class: "assign-to-me-link #{'hide' if issuable.assignees.include?(current_user)}"
diff --git a/app/views/shared/members/_access_request_buttons.html.haml b/app/views/shared/members/_access_request_buttons.html.haml
index d97fdf179d7f9ae599463a61fa391c1c88e51d74..40224cec9e8bebd35bdd28fd27a852dfe7bb5081 100644
--- a/app/views/shared/members/_access_request_buttons.html.haml
+++ b/app/views/shared/members/_access_request_buttons.html.haml
@@ -1,18 +1,20 @@
 - model_name = source.model_name.to_s.downcase
 
-.project-action-button.inline
-  - if can?(current_user, :"destroy_#{model_name}_member", source.members.find_by(user_id: current_user.id))
+- if can?(current_user, :"destroy_#{model_name}_member", source.members.find_by(user_id: current_user.id))
+  .project-action-button.inline
     - link_text = source.is_a?(Group) ? _('Leave group') : _('Leave project')
     = link_to link_text, polymorphic_path([:leave, source, :members]),
               method: :delete,
               data: { confirm: leave_confirmation_message(source) },
               class: 'btn'
-  - elsif requester = source.requesters.find_by(user_id: current_user.id)
+- elsif requester = source.requesters.find_by(user_id: current_user.id)
+  .project-action-button.inline
     = link_to _('Withdraw Access Request'), polymorphic_path([:leave, source, :members]),
               method: :delete,
               data: { confirm: remove_member_message(requester) },
               class: 'btn'
-  - elsif source.request_access_enabled && can?(current_user, :request_access, source)
+- elsif source.request_access_enabled && can?(current_user, :request_access, source)
+  .project-action-button.inline
     = link_to _('Request Access'), polymorphic_path([:request_access, source, :members]),
               method: :post,
               class: 'btn'
diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml
index 1d5a61cffce463d2ab9b3e51092afb6ef9b716d3..bcdad3c153a622629d1db465b3f1b76a2be2e45b 100644
--- a/app/views/shared/members/_group.html.haml
+++ b/app/views/shared/members/_group.html.haml
@@ -14,7 +14,7 @@
         %span{ class: ('text-warning' if group_link.expires_soon?) }
           Expires in #{distance_of_time_in_words_to_now(group_link.expires_at)}
   .controls.member-controls
-    = form_tag namespace_project_group_link_path(@project.namespace, @project, group_link), method: :put, remote: true, class: 'form-horizontal js-edit-member-form' do
+    = form_tag project_group_link_path(@project, group_link), method: :put, remote: true, class: 'form-horizontal js-edit-member-form' do
       = hidden_field_tag "group_link[group_access]", group_link.group_access
       .member-form-control.dropdown.append-right-5
         %button.dropdown-menu-toggle.js-member-permissions-dropdown{ type: "button",
@@ -36,7 +36,7 @@
         = text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}", disabled: !can_admin_member
         %i.clear-icon.js-clear-input
     - if can_admin_member
-      = link_to namespace_project_group_link_path(@project.namespace, @project, group_link),
+      = link_to project_group_link_path(@project, group_link),
         method: :delete,
         data: { confirm: "Are you sure you want to remove #{group.name}?" },
         class: 'btn btn-remove prepend-left-10' do
diff --git a/app/views/shared/milestones/_issuable.html.haml b/app/views/shared/milestones/_issuable.html.haml
index 22547a30cdfb1736678e56163b1acc1c1c5b250b..a7c67ac99803e4468689b564392cbd06550c3a14 100644
--- a/app/views/shared/milestones/_issuable.html.haml
+++ b/app/views/shared/milestones/_issuable.html.haml
@@ -16,7 +16,7 @@
       %strong #{project.name_with_namespace} &middot;
     - if issuable.is_a?(Issue)
       = confidential_icon(issuable)
-    = link_to_gfm issuable.title, issuable_url_args, title: issuable.title
+    = link_to issuable.title, issuable_url_args, title: issuable.title
   .issuable-detail
     = link_to [project.namespace.becomes(Namespace), project, issuable] do
       %span.issuable-number= issuable.to_reference
diff --git a/app/views/shared/milestones/_issuables.html.haml b/app/views/shared/milestones/_issuables.html.haml
index 8af3bd597c5a273b86c7d21870fb9f059ed3f56c..7175e275f95ff12ee7cd445c2afb32d44e0b6186 100644
--- a/app/views/shared/milestones/_issuables.html.haml
+++ b/app/views/shared/milestones/_issuables.html.haml
@@ -8,11 +8,11 @@
       = title
     - if show_counter
       .counter
-        = number_with_delimiter(issuables.size)
+        = number_with_delimiter(issuables.length)
 
   - class_prefix = dom_class(issuables).pluralize
-  %ul{ class: "well-list #{class_prefix}-sortable-list", id: "#{class_prefix}-list-#{id}", "data-state" => id }
+  %ul{ class: "well-list milestone-#{class_prefix}-list", id: "#{class_prefix}-list-#{id}" }
     = render partial: 'shared/milestones/issuable',
-             collection: issuables.order_position_asc,
+             collection: issuables,
              as: :issuable,
              locals: { show_project_name: show_project_name, show_full_project_name: show_full_project_name }
diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml
index 9e6a76e1ddb1e1898fb17224ef8e7207f94ceecf..ecc8b42979cbccfec756867bfd16a7b15a151bfb 100644
--- a/app/views/shared/milestones/_milestone.html.haml
+++ b/app/views/shared/milestones/_milestone.html.haml
@@ -4,7 +4,7 @@
 %li{ class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: custom_dom_id }
   .row
     .col-sm-6
-      %strong= link_to_gfm truncate(milestone.title, length: 100), milestone_path
+      %strong= link_to truncate(milestone.title, length: 100), milestone_path
     .col-sm-6
       .pull-right.light #{milestone.percent_complete(current_user)}% complete
   .row
@@ -35,9 +35,9 @@
       .col-sm-6= render('shared/milestone_expired', milestone: milestone)
       .col-sm-6.milestone-actions
         - if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
-          = link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs btn-grouped" do
+          = link_to edit_project_milestone_path(milestone.project, milestone), class: "btn btn-xs btn-grouped" do
             Edit
           \
-          = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close btn-grouped"
-          = link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-xs btn-remove btn-grouped" do
+          = link_to 'Close Milestone', project_milestone_path(@project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close btn-grouped"
+          = link_to project_milestone_path(milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-xs btn-remove btn-grouped" do
             Delete
diff --git a/app/views/shared/milestones/_sidebar.html.haml b/app/views/shared/milestones/_sidebar.html.haml
index 9bb87640319ad7c79331ec018964ad9acac65bba..895fb8247b5e3452fc4ad9a9d8692d73ec9260de 100644
--- a/app/views/shared/milestones/_sidebar.html.haml
+++ b/app/views/shared/milestones/_sidebar.html.haml
@@ -21,7 +21,7 @@
       .title
         Start date
         - if @project && can?(current_user, :admin_milestone, @project)
-          = link_to 'Edit', edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: 'edit-link pull-right'
+          = link_to 'Edit', edit_project_milestone_path(@project, @milestone), class: 'edit-link pull-right'
       .value
         %span.value-content
           - if milestone.start_date
@@ -51,7 +51,7 @@
       .title.hide-collapsed
         Due date
         - if @project && can?(current_user, :admin_milestone, @project)
-          = link_to 'Edit', edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: 'edit-link pull-right'
+          = link_to 'Edit', edit_project_milestone_path(@project, @milestone), class: 'edit-link pull-right'
       .value.hide-collapsed
         %span.value-content
           - if milestone.due_date
@@ -73,7 +73,7 @@
           Issues
           %span.badge= milestone.issues_visible_to_user(current_user).count
           - if project && can?(current_user, :create_issue, project)
-            = link_to new_namespace_project_issue_path(project.namespace, project, issue: { milestone_id: milestone.id }), class: "pull-right", title: "New Issue" do
+            = link_to new_project_issue_path(project, issue: { milestone_id: milestone.id }), class: "pull-right", title: "New Issue" do
               New issue
         .value.hide-collapsed.bold
           %span.milestone-stat
diff --git a/app/views/shared/milestones/_tabs.html.haml b/app/views/shared/milestones/_tabs.html.haml
index 6a6d817b344e00ac115b57ad5c411ab2202adac0..e2d1695b7c3ad164590d96d67aa1dc2c60f68cd3 100644
--- a/app/views/shared/milestones/_tabs.html.haml
+++ b/app/views/shared/milestones/_tabs.html.haml
@@ -30,13 +30,13 @@
 
 .tab-content.milestone-content
   - if milestone.is_a?(GlobalMilestone) || can?(current_user, :read_issue, @project)
-    .tab-pane.active#tab-issues{ data: { sort_endpoint: (sort_issues_namespace_project_milestone_path(@project.namespace, @project, @milestone) if @project && current_user) } }
-      = render 'shared/milestones/issues_tab', issues: milestone.issues_visible_to_user(current_user).include_associations, show_project_name: show_project_name, show_full_project_name: show_full_project_name
-    .tab-pane#tab-merge-requests{ data: { sort_endpoint: (sort_merge_requests_namespace_project_milestone_path(@project.namespace, @project, @milestone) if @project && current_user) } }
+    .tab-pane.active#tab-issues{ data: { sort_endpoint: (sort_issues_project_milestone_path(@project, @milestone) if @project && current_user) } }
+      = render 'shared/milestones/issues_tab', issues: milestone.sorted_issues(current_user), show_project_name: show_project_name, show_full_project_name: show_full_project_name
+    .tab-pane#tab-merge-requests
       -# loaded async
       = render "shared/milestones/tab_loading"
   - else
-    .tab-pane.active#tab-merge-requests{ data: { sort_endpoint: (sort_merge_requests_namespace_project_milestone_path(@project.namespace, @project, @milestone) if @project && current_user) } }
+    .tab-pane.active#tab-merge-requests
       -# loaded async
       = render "shared/milestones/tab_loading"
   .tab-pane#tab-participants
diff --git a/app/views/shared/milestones/_top.html.haml b/app/views/shared/milestones/_top.html.haml
index 2562f085338f5e8215e124afa3acbc7ab724fc9c..20a12613cfc1ba6ba0930b2f75a446feeb599b28 100644
--- a/app/views/shared/milestones/_top.html.haml
+++ b/app/views/shared/milestones/_top.html.haml
@@ -48,7 +48,7 @@
       %tr
         %td
           - project_name = group ? ms.project.name : ms.project.name_with_namespace
-          = link_to project_name, namespace_project_milestone_path(ms.project.namespace, ms.project, ms)
+          = link_to project_name, project_milestone_path(ms.project, ms)
         %td
           = ms.issues_visible_to_user(current_user).opened.count
         %td
diff --git a/app/views/shared/notes/_form.html.haml b/app/views/shared/notes/_form.html.haml
index eaf50bc2115aa6143d4e84fb509f889f0f9d10e9..c6b5dcc3647d37996840ee331087506cdf439052 100644
--- a/app/views/shared/notes/_form.html.haml
+++ b/app/views/shared/notes/_form.html.haml
@@ -1,6 +1,7 @@
-- supports_slash_commands = note_supports_slash_commands?(@note)
-- if supports_slash_commands
-  - preview_url = preview_markdown_path(@project, slash_commands_target_type: @note.noteable_type, slash_commands_target_id: @note.noteable_id)
+- supports_autocomplete = local_assigns.fetch(:supports_autocomplete, true)
+- supports_quick_actions = note_supports_quick_actions?(@note)
+- if supports_quick_actions
+  - preview_url = preview_markdown_path(@project, quick_actions_target_type: @note.noteable_type, quick_actions_target_id: @note.noteable_id)
 - else
   - preview_url = preview_markdown_path(@project)
 
@@ -27,8 +28,9 @@
       attr: :note,
       classes: 'note-textarea js-note-text',
       placeholder: "Write a comment or drag your files here...",
-      supports_slash_commands: supports_slash_commands
-    = render 'shared/notes/hints', supports_slash_commands: supports_slash_commands
+      supports_quick_actions: supports_quick_actions,
+      supports_autocomplete: supports_autocomplete
+    = render 'shared/notes/hints', supports_quick_actions: supports_quick_actions
     .error-alert
 
   .note-form-actions.clearfix
diff --git a/app/views/shared/notes/_hints.html.haml b/app/views/shared/notes/_hints.html.haml
index 7ce6130de6043eaac973d6d120ea817ee4c77199..bc1ac3d8ac22c4cfb98716f4eb6428f7e6aede92 100644
--- a/app/views/shared/notes/_hints.html.haml
+++ b/app/views/shared/notes/_hints.html.haml
@@ -1,10 +1,10 @@
-- supports_slash_commands = local_assigns.fetch(:supports_slash_commands, false)
+- supports_quick_actions = local_assigns.fetch(:supports_quick_actions, false)
 .comment-toolbar.clearfix
   .toolbar-text
     = link_to 'Markdown', help_page_path('user/markdown'), target: '_blank', tabindex: -1
-    - if supports_slash_commands
+    - if supports_quick_actions
       and
-      = link_to 'slash commands', help_page_path('user/project/slash_commands'), target: '_blank', tabindex: -1
+      = link_to 'quick actions', help_page_path('user/project/quick_actions'), target: '_blank', tabindex: -1
       are
     - else
       is
diff --git a/app/views/shared/notes/_note.html.haml b/app/views/shared/notes/_note.html.haml
index 1e34b7c1e76acd2bef84930de58a4e8a2403ff5e..7174855e176bb6bf121969c426ad660b164a3988 100644
--- a/app/views/shared/notes/_note.html.haml
+++ b/app/views/shared/notes/_note.html.haml
@@ -60,6 +60,6 @@
             = link_to note.attachment.url, target: '_blank' do
               = icon('paperclip')
               = note.attachment_identifier
-              = link_to delete_attachment_namespace_project_note_path(note.project.namespace, note.project, note),
+              = link_to delete_attachment_project_note_path(note.project, note),
                 title: 'Delete this attachment', method: :delete, remote: true, data: { confirm: 'Are you sure you want to remove the attachment?' }, class: 'danger js-note-attachment-delete' do
                 = icon('trash-o', class: 'cred')
diff --git a/app/views/shared/notes/_notes_with_form.html.haml b/app/views/shared/notes/_notes_with_form.html.haml
index 5902798dfd06119d5957412a7866969cda71e707..f0fcc414756b017faf138629a12b86b8d0244768 100644
--- a/app/views/shared/notes/_notes_with_form.html.haml
+++ b/app/views/shared/notes/_notes_with_form.html.haml
@@ -6,13 +6,14 @@
 - if can_create_note?
   %ul.notes.notes-form.timeline
     %li.timeline-entry
-      .flash-container.timeline-content
+      .timeline-entry-inner
+        .flash-container.timeline-content
 
-      .timeline-icon.hidden-xs.hidden-sm
-        %a.author_link{ href: user_path(current_user) }
-          = image_tag avatar_icon(current_user), alt: current_user.to_reference, class: 'avatar s40'
-      .timeline-content.timeline-content-form
-        = render "shared/notes/form", view: diff_view
+        .timeline-icon.hidden-xs.hidden-sm
+          %a.author_link{ href: user_path(current_user) }
+            = image_tag avatar_icon(current_user), alt: current_user.to_reference, class: 'avatar s40'
+        .timeline-content.timeline-content-form
+          = render "shared/notes/form", view: diff_view, supports_autocomplete: autocomplete
 - elsif !current_user
   .disabled-comment.text-center.prepend-top-default
     Please
diff --git a/app/views/shared/notifications/_custom_notifications.html.haml b/app/views/shared/notifications/_custom_notifications.html.haml
index 752932e604520dd9c1e8f61e96809b5f846bd5de..9186c2ba9c9f0692ebb73521c990e531c717d7d3 100644
--- a/app/views/shared/notifications/_custom_notifications.html.haml
+++ b/app/views/shared/notifications/_custom_notifications.html.haml
@@ -3,7 +3,7 @@
     .modal-content
       .modal-header
         %button.close{ type: "button",  "aria-label": "close", data: { dismiss: "modal" } }
-          %span{ "aria-hidden": "true" } } ×
+          %span{ "aria-hidden": "true" } ×
         %h4#custom-notifications-title.modal-title
           #{ _('Custom notification events') }
 
diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml
index aaffc0927ebcab92f4d7c3400fdad42462ae101b..7ed6c622558516bd8be0da8caf36154bcb284ae2 100644
--- a/app/views/shared/projects/_list.html.haml
+++ b/app/views/shared/projects/_list.html.haml
@@ -13,7 +13,7 @@
   - if projects.any?
     %ul.projects-list
       - projects.each_with_index do |project, i|
-        - css_class = (i >= projects_limit) ? 'hide' : nil
+        - css_class = (i >= projects_limit) || project.pending_delete? ? 'hide' : nil
         = render "shared/projects/project", project: project, skip_namespace: skip_namespace,
           avatar: avatar, stars: stars, css_class: css_class, ci: ci, use_creator_avatar: use_creator_avatar,
           forks: forks, show_last_commit_as_description: show_last_commit_as_description
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index fbc335f6176cbdd185a178b6bc5508f9963ca527..4bdbc26a4c3eabe5297491cba7839c9aa8509427 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -7,7 +7,7 @@
 - show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true && project.commit
 - css_class += " no-description" if project.description.blank? && !show_last_commit_as_description
 - cache_key = project_list_cache_key(project)
-- updated_tooltip = time_ago_with_tooltip(project.last_activity_at)
+- updated_tooltip = time_ago_with_tooltip(project.last_activity_date)
 
 %li.project-row{ class: css_class }
   = cache(cache_key) do
@@ -31,7 +31,7 @@
 
       - if show_last_commit_as_description
         .description.prepend-top-5
-          = link_to_gfm project.commit.title, namespace_project_commit_path(project.namespace, project, project.commit),
+          = link_to_gfm project.commit.title, project_commit_path(project, project.commit),
             class: "commit-row-message"
       - elsif project.description.present?
         .description.prepend-top-5
diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml
index 8549cb91b0396ec8bcddcef5cc745919336ab445..43322978749c5dbe390fcb834def9a16e986b58e 100644
--- a/app/views/shared/snippets/_form.html.haml
+++ b/app/views/shared/snippets/_form.html.haml
@@ -36,6 +36,6 @@
         = f.submit 'Save changes', class: "btn-save btn"
 
       - if @snippet.project_id
-        = link_to "Cancel", namespace_project_snippets_path(@project.namespace, @project), class: "btn btn-cancel"
+        = link_to "Cancel", project_snippets_path(@project), class: "btn btn-cancel"
       - else
         = link_to "Cancel", snippets_path(@project), class: "btn btn-cancel"
diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml
index 813d8d69d8d989cb3c295d95d08c391340fbaff5..17b34c5eeb3dc3019bb48a21e9c3d79fe2bd5520 100644
--- a/app/views/shared/snippets/_header.html.haml
+++ b/app/views/shared/snippets/_header.html.haml
@@ -16,7 +16,7 @@
     - else
       = render "snippets/actions"
 
-.snippet-header
+.snippet-header.limited-header-width
   %h2.snippet-title.prepend-top-0.append-bottom-0
     = markdown_field(@snippet, :title)
 
diff --git a/app/views/shared/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml
index 5d2d2317f22697394490fa6db051cf04c3d0630f..7388f20a9fde8203ee862978bfa4094d069dd4ce 100644
--- a/app/views/shared/snippets/_snippet.html.haml
+++ b/app/views/shared/snippets/_snippet.html.haml
@@ -30,7 +30,7 @@
     - if link_project && snippet.project_id?
       %span.hidden-xs
         in
-        = link_to namespace_project_path(snippet.project.namespace, snippet.project) do
+        = link_to project_path(snippet.project) do
           = snippet.project.name_with_namespace
 
     .pull-right.snippet-updated-at
diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml
index 216184eb8392d2929fea3154ebb1bdc8d7182718..8818590362d080961c84fe1360fb76de1942bbd9 100644
--- a/app/views/snippets/show.html.haml
+++ b/app/views/snippets/show.html.haml
@@ -1,3 +1,4 @@
+- @content_class = "limit-container-width limited-inner-width-container" unless fluid_layout
 - page_title "#{@snippet.title} (#{@snippet.to_reference})", "Snippets"
 
 = render 'shared/snippets/header'
@@ -9,4 +10,4 @@
   .row-content-block.top-block.content-component-block
     = render 'award_emoji/awards_block', awardable: @snippet, inline: true
 
-  #notes= render "shared/notes/notes_with_form", :autocomplete => false
+  #notes.limited-width-notes= render "shared/notes/notes_with_form", :autocomplete => false
diff --git a/app/views/users/calendar_activities.html.haml b/app/views/users/calendar_activities.html.haml
index d1e88274878c5873e8e3cdffebf81da96102bcf8..805a346a85e76870241c4d589e5592eb55e70574 100644
--- a/app/views/users/calendar_activities.html.haml
+++ b/app/views/users/calendar_activities.html.haml
@@ -12,7 +12,7 @@
         - if event.push?
           #{event.action_name} #{event.ref_type}
           %strong
-            - commits_path = namespace_project_commits_path(event.project.namespace, event.project, event.ref_name)
+            - commits_path = project_commits_path(event.project, event.ref_name)
             = link_to_if event.project.repository.branch_exists?(event.ref_name), event.ref_name, commits_path
         - else
           = event_action_name(event)
diff --git a/app/workers/expire_job_cache_worker.rb b/app/workers/expire_job_cache_worker.rb
index 08e281e7350163f36c54fd23c51c447fbac20a02..e383202260d24d85cb8d867c440106f47ceba954 100644
--- a/app/workers/expire_job_cache_worker.rb
+++ b/app/workers/expire_job_cache_worker.rb
@@ -18,18 +18,10 @@ class ExpireJobCacheWorker
   private
 
   def project_pipeline_path(project, pipeline)
-    Gitlab::Routing.url_helpers.namespace_project_pipeline_path(
-      project.namespace,
-      project,
-      pipeline,
-      format: :json)
+    Gitlab::Routing.url_helpers.project_pipeline_path(project, pipeline, format: :json)
   end
 
   def project_job_path(project, job)
-    Gitlab::Routing.url_helpers.namespace_project_build_path(
-      project.namespace,
-      project,
-      job.id,
-      format: :json)
+    Gitlab::Routing.url_helpers.project_build_path(project, job.id, format: :json)
   end
 end
diff --git a/app/workers/expire_pipeline_cache_worker.rb b/app/workers/expire_pipeline_cache_worker.rb
index d760f5b140f90dfb4e40a94f48d97d939bc6c048..7c02d6cf8923f6e5ee649ea906f52b81348080fb 100644
--- a/app/workers/expire_pipeline_cache_worker.rb
+++ b/app/workers/expire_pipeline_cache_worker.rb
@@ -23,42 +23,24 @@ class ExpirePipelineCacheWorker
   private
 
   def project_pipelines_path(project)
-    Gitlab::Routing.url_helpers.namespace_project_pipelines_path(
-      project.namespace,
-      project,
-      format: :json)
+    Gitlab::Routing.url_helpers.project_pipelines_path(project, format: :json)
   end
 
   def project_pipeline_path(project, pipeline)
-    Gitlab::Routing.url_helpers.namespace_project_pipeline_path(
-      project.namespace,
-      project,
-      pipeline,
-      format: :json)
+    Gitlab::Routing.url_helpers.project_pipeline_path(project, pipeline, format: :json)
   end
 
   def commit_pipelines_path(project, commit)
-    Gitlab::Routing.url_helpers.pipelines_namespace_project_commit_path(
-      project.namespace,
-      project,
-      commit.id,
-      format: :json)
+    Gitlab::Routing.url_helpers.pipelines_project_commit_path(project, commit.id, format: :json)
   end
 
   def new_merge_request_pipelines_path(project)
-    Gitlab::Routing.url_helpers.new_namespace_project_merge_request_path(
-      project.namespace,
-      project,
-      format: :json)
+    Gitlab::Routing.url_helpers.project_new_merge_request_path(project, format: :json)
   end
 
   def each_pipelines_merge_request_path(project, pipeline)
     pipeline.all_merge_requests.each do |merge_request|
-      path = Gitlab::Routing.url_helpers.pipelines_namespace_project_merge_request_path(
-        project.namespace,
-        project,
-        merge_request,
-        format: :json)
+      path = Gitlab::Routing.url_helpers.pipelines_project_merge_request_path(project, merge_request, format: :json)
 
       yield(path)
     end
diff --git a/app/workers/merge_worker.rb b/app/workers/merge_worker.rb
index 79efca4f2f9443f18e4eb45aa0236f1143bc9358..48e2da338f637a95b8ee04d9c72e5acca6c58b7d 100644
--- a/app/workers/merge_worker.rb
+++ b/app/workers/merge_worker.rb
@@ -7,7 +7,7 @@ class MergeWorker
     current_user = User.find(current_user_id)
     merge_request = MergeRequest.find(merge_request_id)
 
-    MergeRequests::MergeService.new(merge_request.target_project, current_user, params).
-      execute(merge_request)
+    MergeRequests::MergeService.new(merge_request.target_project, current_user, params)
+      .execute(merge_request)
   end
 end
diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb
index 89286595ca6623e27f30a08c5797de2f744c15e4..b8f8d3750d99cdd5d963a96e308c56e389e3a3f4 100644
--- a/app/workers/post_receive.rb
+++ b/app/workers/post_receive.rb
@@ -2,11 +2,11 @@ class PostReceive
   include Sidekiq::Worker
   include DedicatedSidekiqQueue
 
-  def perform(project_identifier, identifier, changes)
-    project, is_wiki = parse_project_identifier(project_identifier)
+  def perform(gl_repository, identifier, changes)
+    project, is_wiki = Gitlab::GlRepository.parse(gl_repository)
 
     if project.nil?
-      log("Triggered hook for non-existing project with identifier \"#{project_identifier}\"")
+      log("Triggered hook for non-existing project with gl_repository \"#{gl_repository}\"")
       return false
     end
 
@@ -59,21 +59,6 @@ class PostReceive
     # Nothing defined here yet.
   end
 
-  # To maintain backwards compatibility, we accept both gl_repository or
-  # repository paths as project identifiers. Our plan is to migrate to
-  # gl_repository only with the following plan:
-  # 9.2: Handle both possible values. Keep Gitlab-Shell sending only repo paths
-  # 9.3 (or patch release): Make GitLab Shell pass gl_repository if present
-  # 9.4 (or patch release): Make GitLab Shell always pass gl_repository
-  # 9.5 (or patch release): Handle only gl_repository as project identifier on this method
-  def parse_project_identifier(project_identifier)
-    if project_identifier.start_with?('/')
-      Gitlab::RepoPath.parse(project_identifier)
-    else
-      Gitlab::GlRepository.parse(project_identifier)
-    end
-  end
-
   def log(message)
     Gitlab::GitLogger.error("POST-RECEIVE: #{message}")
   end
diff --git a/app/workers/process_commit_worker.rb b/app/workers/process_commit_worker.rb
index fe6a49976e0c1196a89b4e81e45e78fc6bd49f3b..c0c03848a403b7d937a53ea5ba7cd6699ac8653a 100644
--- a/app/workers/process_commit_worker.rb
+++ b/app/workers/process_commit_worker.rb
@@ -47,8 +47,8 @@ class ProcessCommitWorker
     # therefor we use IssueCollection here and skip the authorization check in
     # Issues::CloseService#execute.
     IssueCollection.new(issues).updatable_by_user(user).each do |issue|
-      Issues::CloseService.new(project, author).
-        close_issue(issue, commit: commit)
+      Issues::CloseService.new(project, author)
+        .close_issue(issue, commit: commit)
     end
   end
 
@@ -57,8 +57,8 @@ class ProcessCommitWorker
 
     return if mentioned_issues.empty?
 
-    Issue::Metrics.where(issue_id: mentioned_issues.map(&:id), first_mentioned_in_commit_at: nil).
-      update_all(first_mentioned_in_commit_at: commit.committed_date)
+    Issue::Metrics.where(issue_id: mentioned_issues.map(&:id), first_mentioned_in_commit_at: nil)
+      .update_all(first_mentioned_in_commit_at: commit.committed_date)
   end
 
   def build_commit(project, hash)
diff --git a/app/workers/project_cache_worker.rb b/app/workers/project_cache_worker.rb
index 8ff9d07860fdf0ab9eaf7ca85cdca43a0e9b6749..505ff9e086e7d9eee8105022d5bdec0ec66a8077 100644
--- a/app/workers/project_cache_worker.rb
+++ b/app/workers/project_cache_worker.rb
@@ -32,8 +32,8 @@ class ProjectCacheWorker
   private
 
   def try_obtain_lease_for(project_id, section)
-    Gitlab::ExclusiveLease.
-      new("project_cache_worker:#{project_id}:#{section}", timeout: LEASE_TIMEOUT).
-      try_obtain
+    Gitlab::ExclusiveLease
+      .new("project_cache_worker:#{project_id}:#{section}", timeout: LEASE_TIMEOUT)
+      .try_obtain
   end
 end
diff --git a/app/workers/propagate_service_template_worker.rb b/app/workers/propagate_service_template_worker.rb
index 5ce0e0405d0acd412c8df3b5ff18819348ae3485..6b607451c7ab8c826b07df888cc2709837e9d48d 100644
--- a/app/workers/propagate_service_template_worker.rb
+++ b/app/workers/propagate_service_template_worker.rb
@@ -14,8 +14,8 @@ class PropagateServiceTemplateWorker
   private
 
   def try_obtain_lease_for(template_id)
-    Gitlab::ExclusiveLease.
-      new("propagate_service_template_worker:#{template_id}", timeout: LEASE_TIMEOUT).
-      try_obtain
+    Gitlab::ExclusiveLease
+      .new("propagate_service_template_worker:#{template_id}", timeout: LEASE_TIMEOUT)
+      .try_obtain
   end
 end
diff --git a/app/workers/prune_old_events_worker.rb b/app/workers/prune_old_events_worker.rb
index 392abb9c21b43e2476ee37362b3ae7ebc5a09659..2b43bb19ad1a3b97de1283d09bb71aa242fee691 100644
--- a/app/workers/prune_old_events_worker.rb
+++ b/app/workers/prune_old_events_worker.rb
@@ -10,9 +10,9 @@ class PruneOldEventsWorker
       '(id IN (SELECT id FROM (?) ids_to_remove))',
       Event.unscoped.where(
         'created_at < ?',
-        (12.months + 1.day).ago).
-      select(:id).
-      limit(10_000)).
-    delete_all
+        (12.months + 1.day).ago)
+      .select(:id)
+      .limit(10_000))
+    .delete_all
   end
 end
diff --git a/app/workers/repository_check/batch_worker.rb b/app/workers/repository_check/batch_worker.rb
index c3e7491ec4ec9f1f937c9c7c9ee1dc97d16f9bf5..b94d83bd7098313d4fea32df3f39b661114e20c2 100644
--- a/app/workers/repository_check/batch_worker.rb
+++ b/app/workers/repository_check/batch_worker.rb
@@ -32,10 +32,10 @@ module RepositoryCheck
     # has to sit and wait for this query to finish.
     def project_ids
       limit = 10_000
-      never_checked_projects = Project.where('last_repository_check_at IS NULL AND created_at < ?', 24.hours.ago).
-        limit(limit).pluck(:id)
-      old_check_projects = Project.where('last_repository_check_at < ?', 1.month.ago).
-        reorder('last_repository_check_at ASC').limit(limit).pluck(:id)
+      never_checked_projects = Project.where('last_repository_check_at IS NULL AND created_at < ?', 24.hours.ago)
+        .limit(limit).pluck(:id)
+      old_check_projects = Project.where('last_repository_check_at < ?', 1.month.ago)
+        .reorder('last_repository_check_at ASC').limit(limit).pluck(:id)
       never_checked_projects + old_check_projects
     end
 
diff --git a/app/workers/stuck_ci_jobs_worker.rb b/app/workers/stuck_ci_jobs_worker.rb
index ae8c980c9e45d92b8324a1a99bc2bb9625d2a4fa..8b0cfcc8af8057878753b1ac2fbf2fd31009dc10 100644
--- a/app/workers/stuck_ci_jobs_worker.rb
+++ b/app/workers/stuck_ci_jobs_worker.rb
@@ -45,7 +45,7 @@ class StuckCiJobsWorker
 
   def search(status, timeout)
     builds = Ci::Build.where(status: status).where('ci_builds.updated_at < ?', timeout.ago)
-    builds.joins(:project).includes(:tags, :runner, project: :namespace).find_each(batch_size: 50).each do |build|
+    builds.joins(:project).merge(Project.without_deleted).includes(:tags, :runner, project: :namespace).find_each(batch_size: 50).each do |build|
       yield(build)
     end
   end
diff --git a/app/workers/update_user_activity_worker.rb b/app/workers/update_user_activity_worker.rb
index b3c2f13aa33d61adb3d7885093c54d93fec10156..31bbdb69edb18112e756ecd0012f43082d6b2b40 100644
--- a/app/workers/update_user_activity_worker.rb
+++ b/app/workers/update_user_activity_worker.rb
@@ -7,8 +7,8 @@ class UpdateUserActivityWorker
     ids = pairs.keys
     conditions = 'WHEN id = ? THEN ? ' * ids.length
 
-    User.where(id: ids).
-      update_all([
+    User.where(id: ids)
+      .update_all([
         "last_activity_on = CASE #{conditions} ELSE last_activity_on END",
         *pairs.to_a.flatten
       ])
diff --git a/bin/ci/upgrade.rb b/bin/ci/upgrade.rb
deleted file mode 100755
index aab4f60ec605c83fad46d54b33776e8e77b70bf0..0000000000000000000000000000000000000000
--- a/bin/ci/upgrade.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-require_relative "../lib/ci/upgrader"
-
-Ci::Upgrader.new.execute
diff --git a/changelogs/unreleased/10378-promote-blameless-culture.yml b/changelogs/unreleased/10378-promote-blameless-culture.yml
deleted file mode 100644
index 8cf64dfd7936621b8b3b52d0b47fb3c2f08e52d8..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/10378-promote-blameless-culture.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Changed Blame to Annotate in the UI to promote blameless culture
-merge_request: 10378
-author: Ilya Vassilevsky
diff --git a/changelogs/unreleased/12151-add-since-and-until-params-to-issuables.yml b/changelogs/unreleased/12151-add-since-and-until-params-to-issuables.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2c915e62357de13721fcfff00f4b6b85b67b2733
--- /dev/null
+++ b/changelogs/unreleased/12151-add-since-and-until-params-to-issuables.yml
@@ -0,0 +1,4 @@
+---
+title: Added "created_after" and "created_before" params to issuables
+merge_request: 12151
+author: Kyle Bishop @kybishop
diff --git a/changelogs/unreleased/12614-fix-long-message-from-mr.yml b/changelogs/unreleased/12614-fix-long-message-from-mr.yml
deleted file mode 100644
index 30408ea4216a3d4fd38c692167a18900f1557380..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/12614-fix-long-message-from-mr.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Implement web hook logging
-merge_request: 11027
-author: Alexander Randa
diff --git a/changelogs/unreleased/12614-fix-long-message.yml b/changelogs/unreleased/12614-fix-long-message.yml
deleted file mode 100644
index 94f8127c3c1bf67ed281c943da5799a595a5c831..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/12614-fix-long-message.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix long urls in the title of commit
-merge_request: 10938
-author: Alexander Randa
diff --git a/changelogs/unreleased/12910-snippets-description.yml b/changelogs/unreleased/12910-snippets-description.yml
deleted file mode 100644
index ac3d754fee152eadc55a54b684bb3ccc028bd62a..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/12910-snippets-description.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Support descriptions for snippets
-merge_request:
-author:
diff --git a/changelogs/unreleased/14707-allow-activity-feed-to-be-accessible-through-api.yml b/changelogs/unreleased/14707-allow-activity-feed-to-be-accessible-through-api.yml
deleted file mode 100644
index 9c17c3b949c4612bf783f34b0518d772a9a363b0..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/14707-allow-activity-feed-to-be-accessible-through-api.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Introduce an Events API
-merge_request: 11755
-author:
diff --git a/changelogs/unreleased/17489-hide-code-from-guests.yml b/changelogs/unreleased/17489-hide-code-from-guests.yml
deleted file mode 100644
index eb6daffedfe134687e0a0fea516edbb9b41c539d..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/17489-hide-code-from-guests.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Hide clone panel and file list when user is only a guest
-merge_request:
-author: James Clark
diff --git a/changelogs/unreleased/18927-reorder-issue-action-buttons.yml b/changelogs/unreleased/18927-reorder-issue-action-buttons.yml
deleted file mode 100644
index 793d65829406a0ca7e13c9f0e4c1ac7189d432f5..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/18927-reorder-issue-action-buttons.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Reorder Issue action buttons in order of usability
-merge_request: 11642
-author:
diff --git a/changelogs/unreleased/19107-404-when-creating-new-milestone-or-issue-for-project-that-has-issues-disabled.yml b/changelogs/unreleased/19107-404-when-creating-new-milestone-or-issue-for-project-that-has-issues-disabled.yml
deleted file mode 100644
index bec9aa34761374dbf45ea0e5067892e5a6336b19..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/19107-404-when-creating-new-milestone-or-issue-for-project-that-has-issues-disabled.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: 'New issue'/'New merge request' dropdowns should show only projects with issues/merge requests feature enabled
-merge_request: 19107
-author: blackst0ne
diff --git a/changelogs/unreleased/20517-delete-projects-issuescontroller-redirect_old.yml b/changelogs/unreleased/20517-delete-projects-issuescontroller-redirect_old.yml
deleted file mode 100644
index 1f3ab3a2c107188573dd817fb7e867a7a92f61e2..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/20517-delete-projects-issuescontroller-redirect_old.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Remove redirect for old issue url containing id instead of iid
-merge_request: 11135
-author: blackst0ne
diff --git a/changelogs/unreleased/23036-replace-all-spinach-tests-with-rspec-feature-tests.yml b/changelogs/unreleased/23036-replace-all-spinach-tests-with-rspec-feature-tests.yml
deleted file mode 100644
index b350b27d863a4dd8f192ccbbcb31ac4c467cb9d0..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/23036-replace-all-spinach-tests-with-rspec-feature-tests.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Replace 'starred_projects.feature' spinach test with an rspec analog
-merge_request: 11752
-author: blackst0ne
diff --git a/changelogs/unreleased/23036-replace-dashboard-mr-spinach.yml b/changelogs/unreleased/23036-replace-dashboard-mr-spinach.yml
new file mode 100644
index 0000000000000000000000000000000000000000..07c201de96e475e221332156b67e79f2bd553e74
--- /dev/null
+++ b/changelogs/unreleased/23036-replace-dashboard-mr-spinach.yml
@@ -0,0 +1,4 @@
+---
+title: Replace 'dashboard/merge_requests' spinach with rspec
+merge_request: 12440
+author: Alexander Randa (@randaalex)
diff --git a/changelogs/unreleased/23036-replace-dashboard-new-project-spinach.yml b/changelogs/unreleased/23036-replace-dashboard-new-project-spinach.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a5f78202c932b5b1a16d7f316b8de56309dba12a
--- /dev/null
+++ b/changelogs/unreleased/23036-replace-dashboard-new-project-spinach.yml
@@ -0,0 +1,4 @@
+---
+title: Replace 'dashboard/new-project.feature' spinach with rspec
+merge_request: 12550
+author: Alexander Randa (@randaalex)
diff --git a/changelogs/unreleased/23036-replace-dashboard-todo-spinach.yml b/changelogs/unreleased/23036-replace-dashboard-todo-spinach.yml
new file mode 100644
index 0000000000000000000000000000000000000000..65df9a836a525386064552b7d9108cdc1506ef88
--- /dev/null
+++ b/changelogs/unreleased/23036-replace-dashboard-todo-spinach.yml
@@ -0,0 +1,4 @@
+---
+title: Replace 'dashboard/todos' spinach with rspec
+merge_request: 12453
+author: Alexander Randa (@randaalex)
diff --git a/changelogs/unreleased/23036-replace-snippets-spinach.yml b/changelogs/unreleased/23036-replace-snippets-spinach.yml
new file mode 100644
index 0000000000000000000000000000000000000000..545805b1302baedbc773bd17cd73022d756844a2
--- /dev/null
+++ b/changelogs/unreleased/23036-replace-snippets-spinach.yml
@@ -0,0 +1,4 @@
+---
+title: Replace 'snippets/snippets.feature' spinach with rspec
+merge_request: 12385
+author: Alexander Randa @randaalex
diff --git a/changelogs/unreleased/23162-allow-creation-of-files-and-dirs-with-spaces-in-web-ui.yml b/changelogs/unreleased/23162-allow-creation-of-files-and-dirs-with-spaces-in-web-ui.yml
new file mode 100644
index 0000000000000000000000000000000000000000..442406c3c04f90cadd591b42d86120dfbab11989
--- /dev/null
+++ b/changelogs/unreleased/23162-allow-creation-of-files-and-dirs-with-spaces-in-web-ui.yml
@@ -0,0 +1,4 @@
+---
+title: Allow creation of files and directories with spaces through Web UI
+merge_request: 12608
+author:
diff --git a/changelogs/unreleased/23603-add-extra-functionality-for-the-top-right-button.yml b/changelogs/unreleased/23603-add-extra-functionality-for-the-top-right-button.yml
deleted file mode 100644
index 77f8e31e16e14bc8202bd8a65b1f9128eba81325..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/23603-add-extra-functionality-for-the-top-right-button.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add extra context-sensitive functionality for the top right menu button
-merge_request: 11632
-author:
diff --git a/changelogs/unreleased/24032-when-changing-project-visibility-setting-change-other-dropdowns-automatically.yml b/changelogs/unreleased/24032-when-changing-project-visibility-setting-change-other-dropdowns-automatically.yml
deleted file mode 100644
index dbd8a538d517f709ab762fae07717b945bf66af0..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/24032-when-changing-project-visibility-setting-change-other-dropdowns-automatically.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Automatically adjust project settings to match changes in project visibility
-merge_request: 11831
-author:
diff --git a/changelogs/unreleased/24196-protected-variables.yml b/changelogs/unreleased/24196-protected-variables.yml
deleted file mode 100644
index 71567a9d794166d4a370240dc094382e82343f3a..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/24196-protected-variables.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add protected variables which would only be passed to protected branches or
-  protected tags
-merge_request: 11688
-author:
diff --git a/changelogs/unreleased/24373-warning-message-go-away.yml b/changelogs/unreleased/24373-warning-message-go-away.yml
deleted file mode 100644
index c0f2fd260ba052d05e40016806069c8b745e0312..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/24373-warning-message-go-away.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: 'Notes: Warning message should go away once resolved'
-merge_request: 10823
-author: Jacopo Beschi @jacopo-beschi
diff --git a/changelogs/unreleased/25102-files-view-button.yml b/changelogs/unreleased/25102-files-view-button.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4ba815d9464ea73af055234fe62ca1a3c9b07a2c
--- /dev/null
+++ b/changelogs/unreleased/25102-files-view-button.yml
@@ -0,0 +1,4 @@
+---
+title: Fix mobile view of files view buttons
+merge_request:
+author:
diff --git a/changelogs/unreleased/25164-disable-fork-on-project-limit.yml b/changelogs/unreleased/25164-disable-fork-on-project-limit.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9fa824b161d74e41ffcfaa9276f8784037b4cdae
--- /dev/null
+++ b/changelogs/unreleased/25164-disable-fork-on-project-limit.yml
@@ -0,0 +1,4 @@
+---
+title: Disable fork button on project limit
+merge_request: 12145
+author: Ivan Chernov
diff --git a/changelogs/unreleased/25373-jira-links.yml b/changelogs/unreleased/25373-jira-links.yml
deleted file mode 100644
index 09589d4b99294f9e11fef4ce4afeafa6b5403170..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/25373-jira-links.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Don’t create comment on JIRA if it already exists for the entity
-merge_request:
-author:
diff --git a/changelogs/unreleased/25426-group-dashboard-ui.yml b/changelogs/unreleased/25426-group-dashboard-ui.yml
deleted file mode 100644
index cc2bf62d07bd36e0ff5d61829b558e635f24250f..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/25426-group-dashboard-ui.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Update Dashboard Groups UI with better support for subgroups
-merge_request:
-author:
diff --git a/changelogs/unreleased/25680-CI_ENVIRONMENT_URL.yml b/changelogs/unreleased/25680-CI_ENVIRONMENT_URL.yml
deleted file mode 100644
index af9fe3b5041f652753fb7667a56a0872e120c958..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/25680-CI_ENVIRONMENT_URL.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add $CI_ENVIRONMENT_URL to predefined variables for pipelines
-merge_request: 11695
-author:
diff --git a/changelogs/unreleased/26125-match-username-on-search.yml b/changelogs/unreleased/26125-match-username-on-search.yml
new file mode 100644
index 0000000000000000000000000000000000000000..74e918bec1639fd5284262aa12bc7bc6c1a31a8b
--- /dev/null
+++ b/changelogs/unreleased/26125-match-username-on-search.yml
@@ -0,0 +1,5 @@
+---
+title: Inserts exact matches of name, username and email to the top of the search
+  list
+merge_request: 12525
+author:
diff --git a/changelogs/unreleased/26212-upload-user-avatar-trough-api.yml b/changelogs/unreleased/26212-upload-user-avatar-trough-api.yml
new file mode 100644
index 0000000000000000000000000000000000000000..667454ae95d1e07020f698d48f302e95a2204a5a
--- /dev/null
+++ b/changelogs/unreleased/26212-upload-user-avatar-trough-api.yml
@@ -0,0 +1,4 @@
+---
+title: Accept image for avatar in user API
+merge_request: 12143
+author: Ivan Chernov
diff --git a/changelogs/unreleased/26325-system-hooks.yml b/changelogs/unreleased/26325-system-hooks.yml
deleted file mode 100644
index 62b8adaeccd6a409532ab9dc5928554e8a4b729b..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/26325-system-hooks.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: 'Backported new SystemHook event: `repository_update`'
-merge_request: 11140
-author:
diff --git a/changelogs/unreleased/27070-rename-slash-commands-to-quick-actions.yml b/changelogs/unreleased/27070-rename-slash-commands-to-quick-actions.yml
new file mode 100644
index 0000000000000000000000000000000000000000..497239db808180efdaf314febeb77731d052a290
--- /dev/null
+++ b/changelogs/unreleased/27070-rename-slash-commands-to-quick-actions.yml
@@ -0,0 +1,5 @@
+---
+title: Rename "Slash commands" to "Quick actions" and deprecate "chat commands" in favor
+  of "slash commands"
+merge_request:
+author:
diff --git a/changelogs/unreleased/27148-limit-bulk-create-memberships.yml b/changelogs/unreleased/27148-limit-bulk-create-memberships.yml
deleted file mode 100644
index ac4aba2f4e097e307b097ed1fd73b32237cb06a6..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/27148-limit-bulk-create-memberships.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Limit non-administrators to adding 100 members at a time to groups and projects
-merge_request: 11940
-author:
diff --git a/changelogs/unreleased/27439-memory-usage-info.yml b/changelogs/unreleased/27439-memory-usage-info.yml
deleted file mode 100644
index dd212853f57233bad045fbc9abeb548ce80b590a..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/27439-memory-usage-info.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add performance deltas between app deployments on Merge Request widget
-merge_request: 11730
-author:
diff --git a/changelogs/unreleased/27614-improve-instant-comments-exp.yml b/changelogs/unreleased/27614-improve-instant-comments-exp.yml
deleted file mode 100644
index 4db676801f1646a51f6d6edffe67c79512e34cb9..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/27614-improve-instant-comments-exp.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Improve user experience around slash commands in instant comments
-merge_request: 11612
-author:
diff --git a/changelogs/unreleased/27645-html-email-brackets-bug.yml b/changelogs/unreleased/27645-html-email-brackets-bug.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e8004d03884c7e48eb8d0fdc748a9aea1a2e6c5b
--- /dev/null
+++ b/changelogs/unreleased/27645-html-email-brackets-bug.yml
@@ -0,0 +1,4 @@
+---
+title: Fix an email parsing bug where brackets would be inserted in emails from some Outlook clients
+merge_request: 9045
+author: jneen
diff --git a/changelogs/unreleased/27697-make-arrow-icons-consistent-in-dropdown.yml b/changelogs/unreleased/27697-make-arrow-icons-consistent-in-dropdown.yml
new file mode 100644
index 0000000000000000000000000000000000000000..92b5b59f46fb5e8d31db00361217d742627c0fb4
--- /dev/null
+++ b/changelogs/unreleased/27697-make-arrow-icons-consistent-in-dropdown.yml
@@ -0,0 +1,4 @@
+---
+title: Use fa-chevron-down on dropdown arrows for consistency
+merge_request: 9659
+author: TM Lee
diff --git a/changelogs/unreleased/28080-system-checks.yml b/changelogs/unreleased/28080-system-checks.yml
deleted file mode 100644
index 7d83014279aa48813ae1ad2284a80a69988d5127..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/28080-system-checks.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Refactored gitlab:app:check into SystemCheck liberary and improve some checks
-merge_request: 9173
-author:
diff --git a/changelogs/unreleased/28139-use-color-input-broadcast-messages.yml b/changelogs/unreleased/28139-use-color-input-broadcast-messages.yml
new file mode 100644
index 0000000000000000000000000000000000000000..97ebabaff1c3352d606f7c2ac545b9cfe45ab8c4
--- /dev/null
+++ b/changelogs/unreleased/28139-use-color-input-broadcast-messages.yml
@@ -0,0 +1,4 @@
+---
+title: Use color inputs for broadcast messages
+merge_request:
+author:
diff --git a/changelogs/unreleased/28607-forking-and-configuring-project-via-api-works-very-unreliable.yml b/changelogs/unreleased/28607-forking-and-configuring-project-via-api-works-very-unreliable.yml
deleted file mode 100644
index 9cf8d745f92d17c4ef2cf9fd06da54c2fb379fbc..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/28607-forking-and-configuring-project-via-api-works-very-unreliable.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Confirm Project forking behaviour via the API
-merge_request:
-author:
diff --git a/changelogs/unreleased/28694-hard-delete-user-from-admin-panel.yml b/changelogs/unreleased/28694-hard-delete-user-from-admin-panel.yml
deleted file mode 100644
index 2308a5285802fcf4b09e0d58f6455d51e57a0755..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/28694-hard-delete-user-from-admin-panel.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Allow users to be hard-deleted from the admin panel
-merge_request: 11874
-author:
diff --git a/changelogs/unreleased/28694-hard-delete-user-from-api.yml b/changelogs/unreleased/28694-hard-delete-user-from-api.yml
deleted file mode 100644
index ad46540495c339021b55f9703c8d8ab058675fdd..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/28694-hard-delete-user-from-api.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Allow users to be hard-deleted from the API
-merge_request: 11853
-author:
diff --git a/changelogs/unreleased/28717-support-additional-prometheus-metrics.yml b/changelogs/unreleased/28717-support-additional-prometheus-metrics.yml
new file mode 100644
index 0000000000000000000000000000000000000000..720a79b8e1c368d875ee8ade5615982ea2b9de31
--- /dev/null
+++ b/changelogs/unreleased/28717-support-additional-prometheus-metrics.yml
@@ -0,0 +1,4 @@
+---
+title: Additional Prometheus metrics support
+merge_request: 11712
+author:
diff --git a/changelogs/unreleased/29010-perf-bar.yml b/changelogs/unreleased/29010-perf-bar.yml
deleted file mode 100644
index f4167e5562f4c0e7e584ae020cdb2b8574b0c47a..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/29010-perf-bar.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add an optional performance bar to view performance metrics for the current page
-merge_request: 11439
-author:
diff --git a/changelogs/unreleased/29118-add-prometheus-instrumenting-to-gitlab-webapp.yml b/changelogs/unreleased/29118-add-prometheus-instrumenting-to-gitlab-webapp.yml
deleted file mode 100644
index 99c55f128e37e5aa6f91f9c647a399dd7d004f06..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/29118-add-prometheus-instrumenting-to-gitlab-webapp.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add prometheus based metrics collection to gitlab webapp
-merge_request:
-author:
diff --git a/changelogs/unreleased/29690-rotate-otp-key-base.yml b/changelogs/unreleased/29690-rotate-otp-key-base.yml
deleted file mode 100644
index 94d73a2475879151cd9c970b670d2ec043193c44..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/29690-rotate-otp-key-base.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add a Rake task to aid in rotating otp_key_base
-merge_request: 11881
-author:
diff --git a/changelogs/unreleased/29852-latex-formatting.yml b/changelogs/unreleased/29852-latex-formatting.yml
deleted file mode 100644
index e96cda1d6b37de23c823c82589f2392f55a10571..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/29852-latex-formatting.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix LaTeX formatting for AsciiDoc wiki
-merge_request: 11212
-author:
diff --git a/changelogs/unreleased/30213-project-transfer-move-rollback.yml b/changelogs/unreleased/30213-project-transfer-move-rollback.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3eb1e399c545ce697250681189d0f5701f1c4bb1
--- /dev/null
+++ b/changelogs/unreleased/30213-project-transfer-move-rollback.yml
@@ -0,0 +1,4 @@
+---
+title: Rollback project repo move if there is an error in Projects::TransferService
+merge_request: 11877
+author:
diff --git a/changelogs/unreleased/30378-simplified-repository-settings-page.yml b/changelogs/unreleased/30378-simplified-repository-settings-page.yml
deleted file mode 100644
index e8b87c8bb33c59023e69353f3ea928a3e56ad24c..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/30378-simplified-repository-settings-page.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Simplify project repository settings page
-merge_request: 11698
-author:
diff --git a/changelogs/unreleased/30410-revert-9347-and-10079.yml b/changelogs/unreleased/30410-revert-9347-and-10079.yml
deleted file mode 100644
index 0149209caf25a01d9b3c3763546e481ed317edc4..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/30410-revert-9347-and-10079.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Revert the feature that would include the current user's username in the HTTP
-  clone URL
-merge_request: 11792
-author:
diff --git a/changelogs/unreleased/30469-convdev-index.yml b/changelogs/unreleased/30469-convdev-index.yml
deleted file mode 100644
index 0bdd9c4a699540aa78dbc2753258392a4e97e319..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/30469-convdev-index.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add ConvDev Index page to admin area
-merge_request: 11377
-author:
diff --git a/changelogs/unreleased/30651-improve-container-registry-description.yml b/changelogs/unreleased/30651-improve-container-registry-description.yml
deleted file mode 100644
index 0157c9885bce82af552714d300d77d9c41329f81..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/30651-improve-container-registry-description.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add changelog for improved Registry description
-merge_request: 11816
-author:
diff --git a/changelogs/unreleased/30708-stop-using-deleted-at-to-filter-namespaces.yml b/changelogs/unreleased/30708-stop-using-deleted-at-to-filter-namespaces.yml
new file mode 100644
index 0000000000000000000000000000000000000000..83ce3fb4d0ae970c61e404c437cf354580612cb3
--- /dev/null
+++ b/changelogs/unreleased/30708-stop-using-deleted-at-to-filter-namespaces.yml
@@ -0,0 +1,4 @@
+---
+title: Removes deleted_at and pending_delete occurrences in Project related queries
+merge_request: 12091
+author:
diff --git a/changelogs/unreleased/30725-reset-user-limits-when-unchecking-external-user.yml b/changelogs/unreleased/30725-reset-user-limits-when-unchecking-external-user.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3058404b3f886060e056b869da53379032cfc01d
--- /dev/null
+++ b/changelogs/unreleased/30725-reset-user-limits-when-unchecking-external-user.yml
@@ -0,0 +1,4 @@
+---
+title: Ensures default user limits when external user is unchecked
+merge_request: 12218
+author:
diff --git a/changelogs/unreleased/30827-changes-to-audit-log.yml b/changelogs/unreleased/30827-changes-to-audit-log.yml
deleted file mode 100644
index 32db3bf8e9510cc99369fe944565e9db74c5df56..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/30827-changes-to-audit-log.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Renamed users 'Audit Log'' to 'Authentication Log'
-merge_request: 11400
-author:
diff --git a/changelogs/unreleased/30892-add-api-support-for-pipeline-schedule.yml b/changelogs/unreleased/30892-add-api-support-for-pipeline-schedule.yml
deleted file mode 100644
index 26ce84697d065b1c1fb0c37a10751f8142b7ea4e..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/30892-add-api-support-for-pipeline-schedule.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add API support for pipeline schedule
-merge_request: 11307
-author: dosuken123
diff --git a/changelogs/unreleased/30917-wiki-is-not-searchable-with-guest-permissions.yml b/changelogs/unreleased/30917-wiki-is-not-searchable-with-guest-permissions.yml
deleted file mode 100644
index c9bd2dc465e108c0844f95b94fc0c06457487241..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/30917-wiki-is-not-searchable-with-guest-permissions.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: 'Fix: Wiki is not searchable with Guest permissions'
-merge_request:
-author:
diff --git a/changelogs/unreleased/30949-empty-states.yml b/changelogs/unreleased/30949-empty-states.yml
deleted file mode 100644
index bef87a954b7c7bab00f2fb9cebfadad671e369f7..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/30949-empty-states.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Center all empty states
-merge_request:
-author:
diff --git a/changelogs/unreleased/31061-26135-ci-project-slug-enviroment-variables.yml b/changelogs/unreleased/31061-26135-ci-project-slug-enviroment-variables.yml
deleted file mode 100644
index e71910dbd67d42433e1fea7c03c99a7cb9ec56aa..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/31061-26135-ci-project-slug-enviroment-variables.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add slugify project path to CI enviroment variables
-merge_request: 11838
-author: Ivan Chernov
diff --git a/changelogs/unreleased/31384-new-issue-button-on-no-results-page-after-search-doesn-t-go-to-correct-form.yml b/changelogs/unreleased/31384-new-issue-button-on-no-results-page-after-search-doesn-t-go-to-correct-form.yml
deleted file mode 100644
index 8d586616e075fb41e93774ca45934d6a1a01b98e..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/31384-new-issue-button-on-no-results-page-after-search-doesn-t-go-to-correct-form.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Remove 'New issue' button when issues search returns no results.
-merge_request: !11263
-author:
diff --git a/changelogs/unreleased/31448-jira-urls.yml b/changelogs/unreleased/31448-jira-urls.yml
deleted file mode 100644
index d0e39f61b55599191560db697c1443cd02ddf0dc..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/31448-jira-urls.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add API URL to JIRA settings
-merge_request:
-author:
diff --git a/changelogs/unreleased/31474-issue-boards-sidebar-milestone-dropdown-should-not-be-multi-select.yml b/changelogs/unreleased/31474-issue-boards-sidebar-milestone-dropdown-should-not-be-multi-select.yml
deleted file mode 100644
index 88e79e3b6ea0a58719c199294a6b2dc32f27ad9c..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/31474-issue-boards-sidebar-milestone-dropdown-should-not-be-multi-select.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Disallow multiple selections for Milestone dropdown
-merge_request: 11084
-author:
diff --git a/changelogs/unreleased/31483-ordered-task-list.yml b/changelogs/unreleased/31483-ordered-task-list.yml
deleted file mode 100644
index c43915b32680172d71ad79ebff2eec3aff7ab576..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/31483-ordered-task-list.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix Ordered Task List Items
-merge_request: 31483
-author: Jared Deckard <jared.deckard@gmail.com>
diff --git a/changelogs/unreleased/31510-mask-password-field-edit.yml b/changelogs/unreleased/31510-mask-password-field-edit.yml
deleted file mode 100644
index 0ef37be328db18569f97e3e8650f96f52fc07392..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/31510-mask-password-field-edit.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Update password field label while editing service settings
-merge_request: 11431
-author:
diff --git a/changelogs/unreleased/31511-jira-settings.yml b/changelogs/unreleased/31511-jira-settings.yml
deleted file mode 100644
index 4f9ddb13ef61e9cd40cbb0ff7ed65a34f8a63996..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/31511-jira-settings.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Simplify testing and saving service integrations
-merge_request: 11599
-author:
diff --git a/changelogs/unreleased/31554-update-rufus-scheduler-and-sidekiq.yml b/changelogs/unreleased/31554-update-rufus-scheduler-and-sidekiq.yml
deleted file mode 100644
index 0a36b52d5617b7559f3c784c7c4d52a6755a1d4a..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/31554-update-rufus-scheduler-and-sidekiq.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update gem sidekiq-cron from 0.4.4 to 0.6.0 and rufus-scheduler from 3.1.10
-  to 3.4.0
-merge_request: 10976
-author: dosuken123
diff --git a/changelogs/unreleased/31602-display-whether-shared-runner-is-enabled-in-the-admin-dashboard.yml b/changelogs/unreleased/31602-display-whether-shared-runner-is-enabled-in-the-admin-dashboard.yml
deleted file mode 100644
index 00957f7e4f73c640828c08129d1447578780ac0b..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/31602-display-whether-shared-runner-is-enabled-in-the-admin-dashboard.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Display Shared Runner status in Admin Dashboard
-merge_request: 11783
-author: Ivan Chernov
diff --git a/changelogs/unreleased/31616-add-uptime-of-gitlab-instance-in-admin-area.yml b/changelogs/unreleased/31616-add-uptime-of-gitlab-instance-in-admin-area.yml
deleted file mode 100644
index 6dc48d6b2d8be85a9c44e1f85b9500df0b75e5fa..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/31616-add-uptime-of-gitlab-instance-in-admin-area.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add server uptime to System Info page in admin dashboard
-merge_request: 11590
-author: Justin Boltz
diff --git a/changelogs/unreleased/31625-tag-editor-loses-all-inputs-when-you-try-to-add-a-tag-that-already-exists.yml b/changelogs/unreleased/31625-tag-editor-loses-all-inputs-when-you-try-to-add-a-tag-that-already-exists.yml
deleted file mode 100644
index aae760b0ef554598c1d5767bb350fc2f420bdfe8..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/31625-tag-editor-loses-all-inputs-when-you-try-to-add-a-tag-that-already-exists.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Keep input data after creating a tag that already exists
-merge_request: 11155
-author:
diff --git a/changelogs/unreleased/31633-animate-issue.yml b/changelogs/unreleased/31633-animate-issue.yml
deleted file mode 100644
index 6df4135b09cd7fab9cf1d0618fca6b1111ca5e24..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/31633-animate-issue.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: animate adding issue to boards
-merge_request:
-author:
diff --git a/changelogs/unreleased/31644-make-cookie-sessions-unique.yml b/changelogs/unreleased/31644-make-cookie-sessions-unique.yml
deleted file mode 100644
index e9a6a32cf70dea38d9a8754bb7ac9c78c9112132..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/31644-make-cookie-sessions-unique.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Update session cookie key name to be unique to instance in development
-merge_request:
-author:
diff --git a/changelogs/unreleased/31757-single-click-on-filter-in-search-bar-to-activate-dropdown.yml b/changelogs/unreleased/31757-single-click-on-filter-in-search-bar-to-activate-dropdown.yml
deleted file mode 100644
index 48b8a8507ec0fdce880f1278fbb7a127cd2e0472..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/31757-single-click-on-filter-in-search-bar-to-activate-dropdown.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Single click on filter to open filtered search dropdown
-merge_request:
-author:
diff --git a/changelogs/unreleased/31781-print-rendered-files-not-possible.yml b/changelogs/unreleased/31781-print-rendered-files-not-possible.yml
deleted file mode 100644
index 14915823ff77079bce6c0411da12e48ccf66a5e4..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/31781-print-rendered-files-not-possible.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Include the blob content when printing a blob page
-merge_request: 11247
-author:
diff --git a/changelogs/unreleased/31840-add-a-rubocop-that-forbids-redirect_to-inside-a-controller-destroy-action-without-an-explicit-status.yml b/changelogs/unreleased/31840-add-a-rubocop-that-forbids-redirect_to-inside-a-controller-destroy-action-without-an-explicit-status.yml
deleted file mode 100644
index 52bfe771e2bd0e7af503ebb00ce60611cd2640bf..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/31840-add-a-rubocop-that-forbids-redirect_to-inside-a-controller-destroy-action-without-an-explicit-status.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add a rubocop rule to check if a method 'redirect_to' is used without explicitly set 'status' in 'destroy' actions of controllers
-merge_request: 11749
-author: @blackst0ne
diff --git a/changelogs/unreleased/31849-pipeline-real-time-header.yml b/changelogs/unreleased/31849-pipeline-real-time-header.yml
deleted file mode 100644
index 2bb7af897ff08a5249eea950e83695d7d4b7427c..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/31849-pipeline-real-time-header.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Makes header information of pipeline show page realtine
-merge_request:
-author:
diff --git a/changelogs/unreleased/31849-pipeline-show-view-realtime.yml b/changelogs/unreleased/31849-pipeline-show-view-realtime.yml
deleted file mode 100644
index 838a769a26ed870f0bc5ae6c915bc7ef38e6e1ad..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/31849-pipeline-show-view-realtime.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Creates a mediator for pipeline details vue in order to mount several vue apps
-  with the same data
-merge_request:
-author:
diff --git a/changelogs/unreleased/31902-namespace-recent-searches-to-project.yml b/changelogs/unreleased/31902-namespace-recent-searches-to-project.yml
deleted file mode 100644
index e00eb6d8f72ee23b1c74c92d7b9181afe2004e02..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/31902-namespace-recent-searches-to-project.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Scope issue/merge request recent searches to project
-merge_request:
-author:
diff --git a/changelogs/unreleased/3191-deploy-keys-update.yml b/changelogs/unreleased/3191-deploy-keys-update.yml
deleted file mode 100644
index 4100163e94fb1cce250540cb70c8723fe1369747..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/3191-deploy-keys-update.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Implement ability to update deploy keys
-merge_request: 10383
-author: Alexander Randa
diff --git a/changelogs/unreleased/31943-document-go-183.yml b/changelogs/unreleased/31943-document-go-183.yml
deleted file mode 100644
index 201cd48f1ab958f4bc4e36ff84ed41e44e484338..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/31943-document-go-183.yml
+++ /dev/null
@@ -1,3 +0,0 @@
----
-title: Upgrade dependency to Go 1.8.3
-merge_request: 31943
diff --git a/changelogs/unreleased/31983-increase-merge-request-diff-file-size-limit-for-default-toggle-opening.yml b/changelogs/unreleased/31983-increase-merge-request-diff-file-size-limit-for-default-toggle-opening.yml
deleted file mode 100644
index f61aa0a6b6e905f05be6b661b5327bb6cdb132a4..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/31983-increase-merge-request-diff-file-size-limit-for-default-toggle-opening.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Increase individual diff collapse limit to 100 KB, and render limit to 200 KB
-merge_request:
-author:
diff --git a/changelogs/unreleased/31998-pipelines-empty-state.yml b/changelogs/unreleased/31998-pipelines-empty-state.yml
deleted file mode 100644
index 78ae222255e1d09883d5ae375883067864a988bd..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/31998-pipelines-empty-state.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix Pipelines table empty state - only render empty state if we receive 0 pipelines
-merge_request:
-author:
diff --git a/changelogs/unreleased/32048-shared-runners-admin-buttons-have-odd-spacing.yml b/changelogs/unreleased/32048-shared-runners-admin-buttons-have-odd-spacing.yml
new file mode 100644
index 0000000000000000000000000000000000000000..99e64b9b46744c8ddd923d3fc2ed89af76e28af8
--- /dev/null
+++ b/changelogs/unreleased/32048-shared-runners-admin-buttons-have-odd-spacing.yml
@@ -0,0 +1,4 @@
+---
+title: Fix spacing on runner buttons.
+merge_request: !12535
+author:
diff --git a/changelogs/unreleased/32086-atwho-is-still-enabled-for-personal-snippet-comments-form.yml b/changelogs/unreleased/32086-atwho-is-still-enabled-for-personal-snippet-comments-form.yml
deleted file mode 100644
index 0fd248e04007f70427b3a438c66e1d3fbb62125d..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/32086-atwho-is-still-enabled-for-personal-snippet-comments-form.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Disable reference prefixes in notes for Snippets
-merge_request: 11278
-author:
diff --git a/changelogs/unreleased/32118-new-environment-btn-copy.yml b/changelogs/unreleased/32118-new-environment-btn-copy.yml
deleted file mode 100644
index 16a51c3db6a042e3363cce2f536a19366f245f9d..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/32118-new-environment-btn-copy.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Make New environment empty state btn lowercase
-merge_request:
-author:
diff --git a/changelogs/unreleased/32219-speed-up-yarn-install-in-ci-by-utilizing-inter-pipeline-cache.yml b/changelogs/unreleased/32219-speed-up-yarn-install-in-ci-by-utilizing-inter-pipeline-cache.yml
deleted file mode 100644
index 7fb3cb3a30b45410a707e848d40a44f47ce71334..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/32219-speed-up-yarn-install-in-ci-by-utilizing-inter-pipeline-cache.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Cache npm modules between pipelines with yarn to speed up setup-test-env
-merge_request: 11343
-author:
diff --git a/changelogs/unreleased/32301-filter-archive-project-on-param-present.yml b/changelogs/unreleased/32301-filter-archive-project-on-param-present.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d6534ed4e1a9bee30d0560bbeb87599c9da993eb
--- /dev/null
+++ b/changelogs/unreleased/32301-filter-archive-project-on-param-present.yml
@@ -0,0 +1,4 @@
+---
+title: Filter archived project in API v3 only if param present
+merge_request: 12245
+author: Ivan Chernov
diff --git a/changelogs/unreleased/32395-duplicate-string-in-https-docs-gitlab-com-ce-administration-environment_variables-html.yml b/changelogs/unreleased/32395-duplicate-string-in-https-docs-gitlab-com-ce-administration-environment_variables-html.yml
deleted file mode 100644
index d2be3d6cc4b7801ec759cd8782782ea996862919..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/32395-duplicate-string-in-https-docs-gitlab-com-ce-administration-environment_variables-html.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Removes duplicate environment variable in documentation
-merge_request:
-author:
diff --git a/changelogs/unreleased/32408-enable-disable-all-restricted-visibility-levels.yml b/changelogs/unreleased/32408-enable-disable-all-restricted-visibility-levels.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ebb27d118d7404a1187f8c75c70e4e78f7555c94
--- /dev/null
+++ b/changelogs/unreleased/32408-enable-disable-all-restricted-visibility-levels.yml
@@ -0,0 +1,4 @@
+---
+title: Allow admins to disable all restricted visibility levels
+merge_request: 12649
+author:
diff --git a/changelogs/unreleased/32418-make-link-to-self-less-obvious.yml b/changelogs/unreleased/32418-make-link-to-self-less-obvious.yml
deleted file mode 100644
index aabe87dac0fd137a5f739c21e20fc584dcf7264a..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/32418-make-link-to-self-less-obvious.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Change links in issuable meta to black
-merge_request:
-author:
diff --git a/changelogs/unreleased/32570-project-activity-tab-border.yml b/changelogs/unreleased/32570-project-activity-tab-border.yml
deleted file mode 100644
index 100a3e6a74dfad010e0c7dd0da8d5035c7cc1ab3..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/32570-project-activity-tab-border.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix border-bottom for project activity tab
-merge_request:
-author:
diff --git a/changelogs/unreleased/32598-avoid-resource-intensive-login-checks-if-password-is-not-provided-for-git-http.yml b/changelogs/unreleased/32598-avoid-resource-intensive-login-checks-if-password-is-not-provided-for-git-http.yml
deleted file mode 100644
index 6da7491bbdaeab2d2ce76885f7345a84f5988d0e..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/32598-avoid-resource-intensive-login-checks-if-password-is-not-provided-for-git-http.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Avoid resource intensive login checks if password is not provided.
-merge_request: 11537
-author: Horatiu Eugen Vlad
diff --git a/changelogs/unreleased/32642_last_commit_id_in_file_api.yml b/changelogs/unreleased/32642_last_commit_id_in_file_api.yml
deleted file mode 100644
index 80435352e10db2324001aec372276ae421c09e4b..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/32642_last_commit_id_in_file_api.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: 'Introduce optimistic locking support via optional parameter last_commit_sha on File Update API'
-merge_request: 11694
-author: electroma
diff --git a/changelogs/unreleased/32682-skipped-ci-icon.yml b/changelogs/unreleased/32682-skipped-ci-icon.yml
deleted file mode 100644
index ad498b5190074531fb134c14130292277dd20653..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/32682-skipped-ci-icon.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Adds new icon for CI skipped status
-merge_request:
-author:
diff --git a/changelogs/unreleased/32720-emoji-spacing.yml b/changelogs/unreleased/32720-emoji-spacing.yml
deleted file mode 100644
index da3df0f90931aa5374a0d8032926e8fca8dcd229..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/32720-emoji-spacing.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Create equal padding for emoji
-merge_request:
-author:
diff --git a/changelogs/unreleased/32799-remove-no_turbolink-attribute-from-haml.yml b/changelogs/unreleased/32799-remove-no_turbolink-attribute-from-haml.yml
deleted file mode 100644
index 9c1c1fe77f24e8edd9bf97f4aa01be9745a9db74..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/32799-remove-no_turbolink-attribute-from-haml.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Remove redundant data-turbolink attributes from links
-merge_request: 11672
-author: blackst0ne
diff --git a/changelogs/unreleased/32807-company-icon.yml b/changelogs/unreleased/32807-company-icon.yml
deleted file mode 100644
index 718108d3733e42136e0dfd7e70adb28611525596..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/32807-company-icon.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Use briefcase icon for company in profile page
-merge_request:
-author:
diff --git a/changelogs/unreleased/32815--Add-Custom-CI-Config-Path.yml b/changelogs/unreleased/32815--Add-Custom-CI-Config-Path.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7784d7d0ce0dca7c167024776778e6c4d065fcc9
--- /dev/null
+++ b/changelogs/unreleased/32815--Add-Custom-CI-Config-Path.yml
@@ -0,0 +1,4 @@
+---
+title: Allow customize CI config path
+merge_request: 12509
+author: Keith Pope
diff --git a/changelogs/unreleased/32832-confidential-issue-overflow.yml b/changelogs/unreleased/32832-confidential-issue-overflow.yml
deleted file mode 100644
index 7d3d3bfed2e359b798de338cf2f94d4a5c94007e..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/32832-confidential-issue-overflow.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove overflow from comment form for confidential issues and vertically aligns
-  confidential issue icon
-merge_request:
-author:
diff --git a/changelogs/unreleased/32838-admin-panel-spacing.yml b/changelogs/unreleased/32838-admin-panel-spacing.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ccd703fa43fc25b2cc9d5c59166ea8fc59b15443
--- /dev/null
+++ b/changelogs/unreleased/32838-admin-panel-spacing.yml
@@ -0,0 +1,4 @@
+---
+title: Add wells to admin dashboard overview to fix spacing problems
+merge_request:
+author:
diff --git a/changelogs/unreleased/32851-postgres-min-version.yml b/changelogs/unreleased/32851-postgres-min-version.yml
deleted file mode 100644
index 139307d65c6ae216da850a7932f3b67328689e2d..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/32851-postgres-min-version.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Minimum postgresql version is now 9.2
-merge_request: 11677
-author:
diff --git a/changelogs/unreleased/32955-special-keywords.yml b/changelogs/unreleased/32955-special-keywords.yml
deleted file mode 100644
index 0f9939ced8c82b9f9631535e0ac0ec7da5cd0b9d..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/32955-special-keywords.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add all pipeline sources as special keywords to 'only' and 'except'
-merge_request: 11844
-author: Filip Krakowski
diff --git a/changelogs/unreleased/32983-merge-conflict-resolution-removed-the-newline-in-the-end-of-file.yml b/changelogs/unreleased/32983-merge-conflict-resolution-removed-the-newline-in-the-end-of-file.yml
deleted file mode 100644
index eca42176501784478a10363520f8c9fb97d78065..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/32983-merge-conflict-resolution-removed-the-newline-in-the-end-of-file.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Keep trailing newline when resolving conflicts by picking sides
-merge_request:
-author:
diff --git a/changelogs/unreleased/32992-consider-using-zopfli-over-standard-gzip-compression-for-webpack-assets.yml b/changelogs/unreleased/32992-consider-using-zopfli-over-standard-gzip-compression-for-webpack-assets.yml
deleted file mode 100644
index 93037d6181e84ac59cef30a5dfefee8961dd5a22..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/32992-consider-using-zopfli-over-standard-gzip-compression-for-webpack-assets.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Use zopfli compression for frontend assets
-merge_request: 11798
-author:
diff --git a/changelogs/unreleased/33000-tag-list-in-project-create-api.yml b/changelogs/unreleased/33000-tag-list-in-project-create-api.yml
deleted file mode 100644
index b0d0d3cbeba633c4c5bdfc73da745e56f4b4085d..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/33000-tag-list-in-project-create-api.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add tag_list param to project api
-merge_request: 11799
-author: Ivan Chernov
diff --git a/changelogs/unreleased/33032-invalid-you-directly-addressed-yourself-todo-when-using-unsubscribe.yml b/changelogs/unreleased/33032-invalid-you-directly-addressed-yourself-todo-when-using-unsubscribe.yml
deleted file mode 100644
index 1eaa0d0124e828fa07b059decdd8985af3241c7a..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/33032-invalid-you-directly-addressed-yourself-todo-when-using-unsubscribe.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix /unsubscribe slash command creating extra todos when you were already mentioned
-  in an issue
-merge_request:
-author:
diff --git a/changelogs/unreleased/33082-use-update_pipeline_schedule-for-edit-and-take_ownership-in-pipelineschedulescontroller.yml b/changelogs/unreleased/33082-use-update_pipeline_schedule-for-edit-and-take_ownership-in-pipelineschedulescontroller.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d3172c405c335592330cf5cafc0fa9d4b69b10df
--- /dev/null
+++ b/changelogs/unreleased/33082-use-update_pipeline_schedule-for-edit-and-take_ownership-in-pipelineschedulescontroller.yml
@@ -0,0 +1,4 @@
+---
+title: Use authorize_update_pipeline_schedule in PipelineSchedulesController
+merge_request: 11846
+author:
diff --git a/changelogs/unreleased/33130-remove-group-modal.yml b/changelogs/unreleased/33130-remove-group-modal.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4672d41ded5f8dd79364aa8f92b2f51e8a03699f
--- /dev/null
+++ b/changelogs/unreleased/33130-remove-group-modal.yml
@@ -0,0 +1,4 @@
+---
+title: "Remove group modal like remove project modal (requires typing + confirmation)"
+merge_request: 12569
+author: Diego Souza
diff --git a/changelogs/unreleased/33154-permissions-for-project-labels-and-group-labels.yml b/changelogs/unreleased/33154-permissions-for-project-labels-and-group-labels.yml
deleted file mode 100644
index 3b98525167d2eb5f691a3d3b6b3b50d04ed10e45..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/33154-permissions-for-project-labels-and-group-labels.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Allow group reporters to manage group labels
-merge_request:
-author:
diff --git a/changelogs/unreleased/33207-show-delete-option-in-admin-users-page.yml b/changelogs/unreleased/33207-show-delete-option-in-admin-users-page.yml
deleted file mode 100644
index 5eb4e15e31114bac0a7be6182722fa5b2196fedf..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/33207-show-delete-option-in-admin-users-page.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Allow admins to delete users from the admin users page
-merge_request: 11852
-author:
diff --git a/changelogs/unreleased/33215-fix-hard-delete-of-users.yml b/changelogs/unreleased/33215-fix-hard-delete-of-users.yml
deleted file mode 100644
index 29699ff745a21393a364028ddff618ed7b9f9ed9..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/33215-fix-hard-delete-of-users.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix hard-deleting users when they have authored issues
-merge_request: 11855
-author:
diff --git a/changelogs/unreleased/33242-create-project-for-user-api-ignores-path-parameter.yml b/changelogs/unreleased/33242-create-project-for-user-api-ignores-path-parameter.yml
deleted file mode 100644
index c33278998ee1fe52304bac69c7e6ae171e17a8e5..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/33242-create-project-for-user-api-ignores-path-parameter.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix missing optional path parameter in "Create project for user" API
-merge_request: 11868
-author:
diff --git a/changelogs/unreleased/33245-chinese_translation_of_cycle_analytics_page.yml b/changelogs/unreleased/33245-chinese_translation_of_cycle_analytics_page.yml
deleted file mode 100644
index 07dd0872d3b3d7b2a867be882f0ef24c3bc4b4d8..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/33245-chinese_translation_of_cycle_analytics_page.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add Chinese translation of Cycle Analytics Page to I18N
-merge_request: 11644
-author:Huang Tao
diff --git a/changelogs/unreleased/33308-use-pre-wrap-for-commit-messages.yml b/changelogs/unreleased/33308-use-pre-wrap-for-commit-messages.yml
deleted file mode 100644
index 43e8f2429478757c6d78599aec92103cb9fedf36..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/33308-use-pre-wrap-for-commit-messages.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Use pre-wrap for commit messages to keep lists indented
-merge_request:
-author:
diff --git a/changelogs/unreleased/33334-portuguese_brazil_translation_of_cycle_analytics_page.yml b/changelogs/unreleased/33334-portuguese_brazil_translation_of_cycle_analytics_page.yml
deleted file mode 100644
index a0e0458da16ef6b2bea5825f69a6980d3a9eb246..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/33334-portuguese_brazil_translation_of_cycle_analytics_page.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add Portuguese Brazil of Cycle Analytics Page to I18N
-merge_request: 11920
-author:Huang Tao
diff --git a/changelogs/unreleased/33383-bulgarian_translation_of_cycle_analytics_page.yml b/changelogs/unreleased/33383-bulgarian_translation_of_cycle_analytics_page.yml
deleted file mode 100644
index 71bd5505be7b26c48fda4dc2dfc440e4a6f9eaca..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/33383-bulgarian_translation_of_cycle_analytics_page.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: add bulgarian translation of cycle analytics page to I18N
-merge_request: 11958
-author: Lyubomir Vasilev
diff --git a/changelogs/unreleased/33441-supplement_simplified_chinese_translation_of_i18n.yml b/changelogs/unreleased/33441-supplement_simplified_chinese_translation_of_i18n.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a7d8ac9054bf6359eac7f10aa539223c40b0cb2c
--- /dev/null
+++ b/changelogs/unreleased/33441-supplement_simplified_chinese_translation_of_i18n.yml
@@ -0,0 +1,4 @@
+---
+title: Supplement Simplified Chinese translation of Project Page & Repository Page
+merge_request: 11994
+author: Huang Tao
diff --git a/changelogs/unreleased/33442-supplement_traditional_chinese_in_hong_kong_translation_of_i18n.yml b/changelogs/unreleased/33442-supplement_traditional_chinese_in_hong_kong_translation_of_i18n.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e383bab23d6531ae64aaa170b9113b9dbbd010f0
--- /dev/null
+++ b/changelogs/unreleased/33442-supplement_traditional_chinese_in_hong_kong_translation_of_i18n.yml
@@ -0,0 +1,4 @@
+---
+title: Supplement Traditional Chinese in Hong Kong translation of Project Page & Repository Page
+merge_request: 11995
+author: Huang Tao
diff --git a/changelogs/unreleased/33443-supplement_traditional_chinese_in_taiwan_translation_of_i18n.yml b/changelogs/unreleased/33443-supplement_traditional_chinese_in_taiwan_translation_of_i18n.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d6b1b2524c6284ec0cdef5e3393e252dd01b57c7
--- /dev/null
+++ b/changelogs/unreleased/33443-supplement_traditional_chinese_in_taiwan_translation_of_i18n.yml
@@ -0,0 +1,4 @@
+---
+title: Supplement Traditional Chinese in Taiwan translation of Project Page & Repository Page
+merge_request: 12514
+author: Huang Tao
diff --git a/changelogs/unreleased/33445-document-delete-merge-branches-won-t-touch-protected-branches-docs.yml b/changelogs/unreleased/33445-document-delete-merge-branches-won-t-touch-protected-branches-docs.yml
new file mode 100644
index 0000000000000000000000000000000000000000..385f18e25601fbfe20b8a4722682155f695a262c
--- /dev/null
+++ b/changelogs/unreleased/33445-document-delete-merge-branches-won-t-touch-protected-branches-docs.yml
@@ -0,0 +1,4 @@
+---
+title: Document the Delete Merged Branches functionality
+merge_request:
+author:
diff --git a/changelogs/unreleased/instrument-merge-request-diff-load-commits.yml b/changelogs/unreleased/33461-display-user-id.yml
similarity index 50%
rename from changelogs/unreleased/instrument-merge-request-diff-load-commits.yml
rename to changelogs/unreleased/33461-display-user-id.yml
index 916b182a48b389021b21a9399069ee207c13d4a4..cba94625b07c5df01e6185716f18f37a28fa6c01 100644
--- a/changelogs/unreleased/instrument-merge-request-diff-load-commits.yml
+++ b/changelogs/unreleased/33461-display-user-id.yml
@@ -1,4 +1,4 @@
 ---
-title: Instrument MergeRequestDiff#load_commits
-merge_request:
-author:
+title: Display own user id in account settings page
+merge_request: 12141
+author: Riccardo Padovani
diff --git a/changelogs/unreleased/33538-update-ci-dockerfile-now-that-chrome-headless-no-longer-in-beta.yml b/changelogs/unreleased/33538-update-ci-dockerfile-now-that-chrome-headless-no-longer-in-beta.yml
new file mode 100644
index 0000000000000000000000000000000000000000..590472c099088cc3e536030efa3b625b90eec8d0
--- /dev/null
+++ b/changelogs/unreleased/33538-update-ci-dockerfile-now-that-chrome-headless-no-longer-in-beta.yml
@@ -0,0 +1,4 @@
+---
+title: Update QA Dockerfile to lock Chrome browser version
+merge_request: 12071
+author:
diff --git a/changelogs/unreleased/33561-supplement_bulgarian_translation_of_i18n.yml b/changelogs/unreleased/33561-supplement_bulgarian_translation_of_i18n.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4f2ba2e1de33a72ffa409f153b33931500bb509b
--- /dev/null
+++ b/changelogs/unreleased/33561-supplement_bulgarian_translation_of_i18n.yml
@@ -0,0 +1,4 @@
+---
+title: Supplement Bulgarian translation of Project Page & Repository Page
+merge_request: 12083
+author: Lyubomir Vasilev
diff --git a/changelogs/unreleased/33580-fix-api-scoping.yml b/changelogs/unreleased/33580-fix-api-scoping.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f4ebb13c082622eeb82c1da4ac6d06b09e657504
--- /dev/null
+++ b/changelogs/unreleased/33580-fix-api-scoping.yml
@@ -0,0 +1,4 @@
+---
+title: Fix API Scoping
+merge_request: 12300
+author:
diff --git a/changelogs/unreleased/33772-readonly-gitlab-ci-cache.yml b/changelogs/unreleased/33772-readonly-gitlab-ci-cache.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c2bce368a586958afc409e5f228f24fbf2a7b387
--- /dev/null
+++ b/changelogs/unreleased/33772-readonly-gitlab-ci-cache.yml
@@ -0,0 +1,4 @@
+---
+title: Introduce cache policies for CI jobs
+merge_request: 12483
+author:
diff --git a/changelogs/unreleased/33837-remove-trash-on-registry-image.yml b/changelogs/unreleased/33837-remove-trash-on-registry-image.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2d337f5e6e41bb4c4f7e63bc1d52fc36b153c8bd
--- /dev/null
+++ b/changelogs/unreleased/33837-remove-trash-on-registry-image.yml
@@ -0,0 +1,4 @@
+---
+title: Remove registry image delete button if user cant delete it
+merge_request: 12317
+author: Ivan Chernov
diff --git a/changelogs/unreleased/33846-no-runner-for-admin.yml b/changelogs/unreleased/33846-no-runner-for-admin.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a2d46802c61b7cd0fb83147da5ba948e2ee11cf7
--- /dev/null
+++ b/changelogs/unreleased/33846-no-runner-for-admin.yml
@@ -0,0 +1,4 @@
+---
+title: Add explicit message when no runners on admin
+merge_request: 12266
+author: Takuya Noguchi
diff --git a/changelogs/unreleased/34052-store-mr-ref-fetched-in-database.yml b/changelogs/unreleased/34052-store-mr-ref-fetched-in-database.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4bacfca7551bf33a3fb3ee9f10533194c4ffa1e5
--- /dev/null
+++ b/changelogs/unreleased/34052-store-mr-ref-fetched-in-database.yml
@@ -0,0 +1,4 @@
+---
+title: Store merge request ref_fetched status in the database
+merge_request: 12424
+author:
diff --git a/changelogs/unreleased/34078-allow-to-enable-feature-flags-with-more-granularity.yml b/changelogs/unreleased/34078-allow-to-enable-feature-flags-with-more-granularity.yml
new file mode 100644
index 0000000000000000000000000000000000000000..69d5d34b07258b0d71f035cdeb69d3d45967d72b
--- /dev/null
+++ b/changelogs/unreleased/34078-allow-to-enable-feature-flags-with-more-granularity.yml
@@ -0,0 +1,4 @@
+---
+title: Allow the feature flags to be enabled/disabled with more granularity
+merge_request: 12357
+author:
diff --git a/changelogs/unreleased/34116-milestone-filtering-on-group-issues.yml b/changelogs/unreleased/34116-milestone-filtering-on-group-issues.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8f8b5a96c2b6577e9f293265a5aa73e9b5ae8782
--- /dev/null
+++ b/changelogs/unreleased/34116-milestone-filtering-on-group-issues.yml
@@ -0,0 +1,4 @@
+---
+title: Change milestone endpoint for groups
+merge_request: 12374
+author: Takuya Noguchi
diff --git a/changelogs/unreleased/34141-allow-unauthenticated-access-to-the-users-api.yml b/changelogs/unreleased/34141-allow-unauthenticated-access-to-the-users-api.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a3ade8db214d21c46335ba0fb7344573638fba82
--- /dev/null
+++ b/changelogs/unreleased/34141-allow-unauthenticated-access-to-the-users-api.yml
@@ -0,0 +1,4 @@
+---
+title: Allow unauthenticated access to the /api/v4/users API
+merge_request: 12445
+author:
diff --git a/changelogs/unreleased/34169-add-simplified-chinese-translations-of-commits-page.yml b/changelogs/unreleased/34169-add-simplified-chinese-translations-of-commits-page.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1a631c3f0a417478c278cdf21b707dbf4bc46e06
--- /dev/null
+++ b/changelogs/unreleased/34169-add-simplified-chinese-translations-of-commits-page.yml
@@ -0,0 +1,4 @@
+---
+title: Add Simplified Chinese translations of Commits Page
+merge_request: 12405
+author: Huang Tao
diff --git a/changelogs/unreleased/34171-add-traditional-chinese-in-hongkong-translations-of-commits-page.yml b/changelogs/unreleased/34171-add-traditional-chinese-in-hongkong-translations-of-commits-page.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3cf7c0b547fc4588c8cf39de195511d2efecb7a8
--- /dev/null
+++ b/changelogs/unreleased/34171-add-traditional-chinese-in-hongkong-translations-of-commits-page.yml
@@ -0,0 +1,4 @@
+---
+title: Add Traditional Chinese in HongKong translations of Commits Page
+merge_request: 12406
+author: Huang Tao
diff --git a/changelogs/unreleased/34175-add-esperanto-translations-of-commits-page.yml b/changelogs/unreleased/34175-add-esperanto-translations-of-commits-page.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b43a38f3794b4a45cfcf4f416ec400f125e8eef8
--- /dev/null
+++ b/changelogs/unreleased/34175-add-esperanto-translations-of-commits-page.yml
@@ -0,0 +1,4 @@
+---
+title: Add Esperanto translations of Commits Page
+merge_request: 12410
+author: Huang Tao
diff --git a/changelogs/unreleased/34176-add-bulgarian-translations-of-commits-page.yml b/changelogs/unreleased/34176-add-bulgarian-translations-of-commits-page.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9177ae3acd1e986ce543832884be34803f4ecb4a
--- /dev/null
+++ b/changelogs/unreleased/34176-add-bulgarian-translations-of-commits-page.yml
@@ -0,0 +1,4 @@
+---
+title: Add Bulgarian translations of Commits Page
+merge_request: 12411
+author: Huang Tao
diff --git a/changelogs/unreleased/34207-remove-bin-ci-upgrade-rb.yml b/changelogs/unreleased/34207-remove-bin-ci-upgrade-rb.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4fa385c3c27a4991da296f09055b34a1e6ea0fb5
--- /dev/null
+++ b/changelogs/unreleased/34207-remove-bin-ci-upgrade-rb.yml
@@ -0,0 +1,4 @@
+---
+title: Remove bin/ci/upgrade.rb as not working all
+merge_request: 12414
+author: Takuya Noguchi
diff --git a/changelogs/unreleased/34286-add-esperanto-translations-for-cycle-analytics-and-project-and-repository-pages.yml b/changelogs/unreleased/34286-add-esperanto-translations-for-cycle-analytics-and-project-and-repository-pages.yml
new file mode 100644
index 0000000000000000000000000000000000000000..af743f3e506c3535ad7664c511e32bb57f76c8fa
--- /dev/null
+++ b/changelogs/unreleased/34286-add-esperanto-translations-for-cycle-analytics-and-project-and-repository-pages.yml
@@ -0,0 +1,4 @@
+---
+title: Add Esperanto translations for Cycle Analytics, Project, and Repository pages
+merge_request: 12442
+author: Huang Tao
diff --git a/changelogs/unreleased/34289-drop-gfm-on-milestone-issuable-title.yml b/changelogs/unreleased/34289-drop-gfm-on-milestone-issuable-title.yml
new file mode 100644
index 0000000000000000000000000000000000000000..42e906d24c63a003d7fa0d764babe4c926812ef5
--- /dev/null
+++ b/changelogs/unreleased/34289-drop-gfm-on-milestone-issuable-title.yml
@@ -0,0 +1,4 @@
+---
+title: Drop GFM support for issuable title on milestone for consistency and performance
+merge_request:
+author: Takuya Noguchi
diff --git a/changelogs/unreleased/34309-drop-gfm-mr-ms.yml b/changelogs/unreleased/34309-drop-gfm-mr-ms.yml
new file mode 100644
index 0000000000000000000000000000000000000000..07fe79e90ee6907a2c7db0c8a36a163094cdc959
--- /dev/null
+++ b/changelogs/unreleased/34309-drop-gfm-mr-ms.yml
@@ -0,0 +1,4 @@
+---
+title: Drop GFM support for the title of Milestone/MergeRequest in template
+merge_request: 12451
+author: Takuya Noguchi
diff --git a/changelogs/unreleased/34403-issue-dropdown-persists-when-adding-issue-number-to-issue-description.yml b/changelogs/unreleased/34403-issue-dropdown-persists-when-adding-issue-number-to-issue-description.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4911315d018afaa6bd558787ce150e7b895d4053
--- /dev/null
+++ b/changelogs/unreleased/34403-issue-dropdown-persists-when-adding-issue-number-to-issue-description.yml
@@ -0,0 +1,4 @@
+---
+title: Closes any open Autocomplete of the markdown editor when the form is closed
+merge_request: 12521
+author: 
diff --git a/changelogs/unreleased/34531-remove-scroll.yml b/changelogs/unreleased/34531-remove-scroll.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c3c5289f66f4de373ca39e33372b6be1f5314e4e
--- /dev/null
+++ b/changelogs/unreleased/34531-remove-scroll.yml
@@ -0,0 +1,4 @@
+---
+title: Update jobs page output to have a scrollable page
+merge_request: 12587
+author:
diff --git a/changelogs/unreleased/34544-add-italian-translation-of-cycle-analytics-page-&-project-page-&-repository-page.yml b/changelogs/unreleased/34544-add-italian-translation-of-cycle-analytics-page-&-project-page-&-repository-page.yml
new file mode 100644
index 0000000000000000000000000000000000000000..31f4262c9f97a105fb646666b703f11a335557ad
--- /dev/null
+++ b/changelogs/unreleased/34544-add-italian-translation-of-cycle-analytics-page-&-project-page-&-repository-page.yml
@@ -0,0 +1,4 @@
+---
+title: Add Italian translation of Cycle Analytics Page & Project Page & Repository Page
+merge_request: 12578
+author: Huang Tao
diff --git a/changelogs/unreleased/34578-sidebar-padding.yml b/changelogs/unreleased/34578-sidebar-padding.yml
new file mode 100644
index 0000000000000000000000000000000000000000..dc4647298e67eab5865e4554d942928290b1355e
--- /dev/null
+++ b/changelogs/unreleased/34578-sidebar-padding.yml
@@ -0,0 +1,4 @@
+---
+title: fix left & right padding on sidebar
+merge_request:
+author:
diff --git a/changelogs/unreleased/34688-add-italian-translations-of-commits-page.yml b/changelogs/unreleased/34688-add-italian-translations-of-commits-page.yml
new file mode 100644
index 0000000000000000000000000000000000000000..90a1f8c98fe2bf32e6d7b89c585fd803cb55fad8
--- /dev/null
+++ b/changelogs/unreleased/34688-add-italian-translations-of-commits-page.yml
@@ -0,0 +1,4 @@
+---
+title: Add Italian translations of Commits Page
+merge_request: 12645
+author: Huang Tao
diff --git a/changelogs/unreleased/UI-improvements-for-count-badges-and-permission-badges.yml b/changelogs/unreleased/UI-improvements-for-count-badges-and-permission-badges.yml
deleted file mode 100644
index 374f643faa7423b8431665140b7b7abb01a5115c..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/UI-improvements-for-count-badges-and-permission-badges.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Count badges depend on translucent color to better adjust to different background
-  colors and permission badges now feature a pill shaped design similar to labels
-merge_request:
-author:
diff --git a/changelogs/unreleased/adam-external-issue-references-spike.yml b/changelogs/unreleased/adam-external-issue-references-spike.yml
new file mode 100644
index 0000000000000000000000000000000000000000..aeec66884259e242cf3d61c5b6a0a4b66d581e03
--- /dev/null
+++ b/changelogs/unreleased/adam-external-issue-references-spike.yml
@@ -0,0 +1,4 @@
+---
+title: Improve support for external issue references
+merge_request: 12485
+author:
diff --git a/changelogs/unreleased/adam-influxdb-hostname.yml b/changelogs/unreleased/adam-influxdb-hostname.yml
deleted file mode 100644
index ab201ae789424843257ad5a8434c628dce9434ba..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/adam-influxdb-hostname.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Allow GitLab instance to start when InfluxDB hostname cannot be resolved
-merge_request: 11356
-author:
diff --git a/changelogs/unreleased/add-ci_variables-environment_scope-mysql.yml b/changelogs/unreleased/add-ci_variables-environment_scope-mysql.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4948d415bed5001ce4c0c7509c6d01e1c06f94e2
--- /dev/null
+++ b/changelogs/unreleased/add-ci_variables-environment_scope-mysql.yml
@@ -0,0 +1,6 @@
+---
+title: Rename duplicated variables with the same key for projects. Add environment_scope
+  column to variables and add unique constraint to make sure that no variables could
+  be created with the same key within a project
+merge_request: 12363
+author:
diff --git a/changelogs/unreleased/add-group-members-counting-and-plan-related-data-on-namespaces-api.yml b/changelogs/unreleased/add-group-members-counting-and-plan-related-data-on-namespaces-api.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f2591042e98399b04b4dc03874a32c5b5e9e3d73
--- /dev/null
+++ b/changelogs/unreleased/add-group-members-counting-and-plan-related-data-on-namespaces-api.yml
@@ -0,0 +1,4 @@
+---
+title: Add group members counting and plan related data on namespaces API
+merge_request:
+author:
diff --git a/changelogs/unreleased/add-index-for-auto_canceled_by_id-mysql.yml b/changelogs/unreleased/add-index-for-auto_canceled_by_id-mysql.yml
deleted file mode 100644
index eac78e9ee1fcb2164231d986fb9fca4fd089ab02..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/add-index-for-auto_canceled_by_id-mysql.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add indices for auto_canceled_by_id for ci_pipelines and ci_builds on PostgreSQL
-merge_request: 11034
-author:
diff --git a/changelogs/unreleased/add-unicode-trace-feature-test.yml b/changelogs/unreleased/add-unicode-trace-feature-test.yml
deleted file mode 100644
index 90c6a9afefcbd4bb1dead0f4f2b1bf5796425687..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/add-unicode-trace-feature-test.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add a feature test for Unicode trace
-merge_request: 10736
-author: dosuken123
diff --git a/changelogs/unreleased/add_ability_to_cancel_attaching_file_and_redesign_attaching_files_ui.yml b/changelogs/unreleased/add_ability_to_cancel_attaching_file_and_redesign_attaching_files_ui.yml
deleted file mode 100644
index fcf4efa284639511948e1f5ec6c46d9ec7bebb63..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/add_ability_to_cancel_attaching_file_and_redesign_attaching_files_ui.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add an ability to cancel attaching file and redesign attaching files UI
-merge_request: 9431
-author: blackst0ne
diff --git a/changelogs/unreleased/aliyun-backup-provider.yml b/changelogs/unreleased/aliyun-backup-provider.yml
deleted file mode 100644
index e7505e44a59e3fdd26e6515fb71e98914cad68bc..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/aliyun-backup-provider.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add Aliyun OSS as the backup storage provider
-merge_request: 9721
-author: Yuanfei Zhu
diff --git a/changelogs/unreleased/allow-reporters-to-promote-group-labels.yml b/changelogs/unreleased/allow-reporters-to-promote-group-labels.yml
deleted file mode 100644
index 2364ce6d068534b51997806380775dd2b3bbbaab..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/allow-reporters-to-promote-group-labels.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Allow reporters to promote project labels to group labels
-merge_request:
-author:
diff --git a/changelogs/unreleased/allow_numeric_pages_domain.yml b/changelogs/unreleased/allow_numeric_pages_domain.yml
deleted file mode 100644
index 10d9f26f88d81e20f5b3f2a6849794768e9deeed..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/allow_numeric_pages_domain.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Allow numeric pages domain
-merge_request: 11550
-author:
diff --git a/changelogs/unreleased/allow_numeric_values_in_gitlab_ci_yml.yml b/changelogs/unreleased/allow_numeric_values_in_gitlab_ci_yml.yml
deleted file mode 100644
index 8c7fa53a18b89d7c512f04eb28d94ccd585fc415..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/allow_numeric_values_in_gitlab_ci_yml.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Allow numeric values in gitlab-ci.yml
-merge_request: 10607
-author: blackst0ne
diff --git a/changelogs/unreleased/artifacts-keyboard-shortcuts.yml b/changelogs/unreleased/artifacts-keyboard-shortcuts.yml
deleted file mode 100644
index 69569504c4f7baa8e8c1192009a0b5cd3fd8bc1f..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/artifacts-keyboard-shortcuts.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Enabled keyboard shortcuts on artifacts pages
-merge_request:
-author:
diff --git a/changelogs/unreleased/auto-search-when-state-changed.yml b/changelogs/unreleased/auto-search-when-state-changed.yml
deleted file mode 100644
index 2723beb8600eab48c30b573eeeffda6f966c1caa..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/auto-search-when-state-changed.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Perform filtered search when state tab is changed
-merge_request:
-author:
diff --git a/changelogs/unreleased/bugfix-v3-deploy_keys-can_push.yml b/changelogs/unreleased/bugfix-v3-deploy_keys-can_push.yml
deleted file mode 100644
index 0306663ac8dc6a1cf334521393e43996bdfe6e16..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/bugfix-v3-deploy_keys-can_push.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: "Fixed handling of the `can_push` attribute in the v3 deploy_keys api"
-merge_request: 11607
-author: Richard Clamp
diff --git a/changelogs/unreleased/bvl-rename-all-reserved-paths.yml b/changelogs/unreleased/bvl-rename-all-reserved-paths.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f37f2fa94ae096d82afa42e8daaf9d55eb5ebc52
--- /dev/null
+++ b/changelogs/unreleased/bvl-rename-all-reserved-paths.yml
@@ -0,0 +1,4 @@
+---
+title: Rename all reserved paths that could have been created
+merge_request: 11713
+author:
diff --git a/changelogs/unreleased/bvl-rename-build-events-to-job-events.yml b/changelogs/unreleased/bvl-rename-build-events-to-job-events.yml
deleted file mode 100644
index 2ce01a71361a5e68be3fbad00afaef47cc8d0ce2..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/bvl-rename-build-events-to-job-events.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Rename build_events to job_events
-merge_request: 11287
-author:
diff --git a/changelogs/unreleased/bvl-translate-project-pages.yml b/changelogs/unreleased/bvl-translate-project-pages.yml
deleted file mode 100644
index fb90aba08b48e826fe0fd5a5c34a425fbc91463c..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/bvl-translate-project-pages.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Translate backend for Project & Repository pages
-merge_request: 11183
-author:
diff --git a/changelogs/unreleased/ce-31853-projects-shared-groups.yml b/changelogs/unreleased/ce-31853-projects-shared-groups.yml
deleted file mode 100644
index ffa3aed682d091e6a7c1706ee5ad6841947dda44..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/ce-31853-projects-shared-groups.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Remove duplication for sharing projects with groups in project settings
-merge_request:
-author:
diff --git a/changelogs/unreleased/ce-32623-browser-tooltip-commits-branch-list.yml b/changelogs/unreleased/ce-32623-browser-tooltip-commits-branch-list.yml
deleted file mode 100644
index 93edafed699f6c12ecfefafe64975ae273b99dbf..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/ce-32623-browser-tooltip-commits-branch-list.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Change order of commits ahead and behind on divergence graph for branch list
-  view
-merge_request:
-author:
diff --git a/changelogs/unreleased/ci-build-pipeline-header-vue.yml b/changelogs/unreleased/ci-build-pipeline-header-vue.yml
deleted file mode 100644
index 2bbff2fdd161cb02e9a92ce66e5afbfb3f9b4fcf..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/ci-build-pipeline-header-vue.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Creates CI Header component for Pipelines and Jobs details pages
-merge_request:
-author:
diff --git a/changelogs/unreleased/commit-comments-limited-width.yml b/changelogs/unreleased/commit-comments-limited-width.yml
new file mode 100644
index 0000000000000000000000000000000000000000..97f501054956b8084bc1f8dad6c2faf8705e6d3d
--- /dev/null
+++ b/changelogs/unreleased/commit-comments-limited-width.yml
@@ -0,0 +1,4 @@
+---
+title: Limit commit & snippets comments width
+merge_request:
+author:
diff --git a/changelogs/unreleased/disable-blocked-manual-actions.yml b/changelogs/unreleased/disable-blocked-manual-actions.yml
deleted file mode 100644
index a640f61a7ddb10f90d41712fbe426672b1fa5e24..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/disable-blocked-manual-actions.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: disable blocked manual actions
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-async-tree-readme.yml b/changelogs/unreleased/dm-async-tree-readme.yml
deleted file mode 100644
index fb1cfeb210a81b4bd6ca0b783a1a45e36ed09276..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/dm-async-tree-readme.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Load tree readme asynchronously
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-auxiliary-viewers.yml b/changelogs/unreleased/dm-auxiliary-viewers.yml
deleted file mode 100644
index ba73a4991158db13553a111ac4069a59fc5e85b8..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/dm-auxiliary-viewers.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Display extra info about files on .gitlab-ci.yml, .gitlab/route-map.yml and
-  LICENSE blob pages
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-comment-on-mr-commit-discussion.yml b/changelogs/unreleased/dm-comment-on-mr-commit-discussion.yml
deleted file mode 100644
index 50db66c89baba778c1ea1e3dc7dafba5b4ba5d63..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/dm-comment-on-mr-commit-discussion.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix replying to a commit discussion displayed in the context of an MR
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-commit-row-browse-button.yml b/changelogs/unreleased/dm-commit-row-browse-button.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4240a7de5de2f799c6481d25e46b77f241d47145
--- /dev/null
+++ b/changelogs/unreleased/dm-commit-row-browse-button.yml
@@ -0,0 +1,4 @@
+---
+title: Fix inconsistent display of the "Browse files" button in the commit list
+merge_request:
+author:
diff --git a/changelogs/unreleased/dm-consistent-commit-sha-style.yml b/changelogs/unreleased/dm-consistent-commit-sha-style.yml
deleted file mode 100644
index b6dace34d9b41d182cccbcad6bdbf41b2c411b70..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/dm-consistent-commit-sha-style.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Consistently use monospace font for commit SHAs and branch and tag names
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-consistent-last-push-event.yml b/changelogs/unreleased/dm-consistent-last-push-event.yml
deleted file mode 100644
index acc17cb45236bb742330395a62a73345dae9d7e9..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/dm-consistent-last-push-event.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Consistently display last push event widget
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-copy-as-gfm-without-empty-elements.yml b/changelogs/unreleased/dm-copy-as-gfm-without-empty-elements.yml
deleted file mode 100644
index 45a61320ff27fa9838ca6d02abbc0df9933d4293..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/dm-copy-as-gfm-without-empty-elements.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Don't copy empty elements that were not selected on purpose as GFM
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-copy-gfm-when-parts-of-other-elements-are-selected.yml b/changelogs/unreleased/dm-copy-gfm-when-parts-of-other-elements-are-selected.yml
deleted file mode 100644
index ae916c30ff835149f2f8128d39a331914fc6b16b..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/dm-copy-gfm-when-parts-of-other-elements-are-selected.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Copy as GFM even when parts of other elements are selected
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-dependency-linker-gemfile.yml b/changelogs/unreleased/dm-dependency-linker-gemfile.yml
deleted file mode 100644
index 2d4167a1be5ef189345ca2112bef1ee425b3ef23..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/dm-dependency-linker-gemfile.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Autolink package names in Gemfile
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-discussions-n-plus-1.yml b/changelogs/unreleased/dm-discussions-n-plus-1.yml
deleted file mode 100644
index b97e434424817f14ef19a748744980e1276ae2da..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/dm-discussions-n-plus-1.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Resolve N+1 query issue with discussions
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-drop-default-scope-on-sortable-finders.yml b/changelogs/unreleased/dm-drop-default-scope-on-sortable-finders.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b359a25053a79498e5e7cee8b441774dfc52cacc
--- /dev/null
+++ b/changelogs/unreleased/dm-drop-default-scope-on-sortable-finders.yml
@@ -0,0 +1,4 @@
+---
+title: Improve performance of lookups of issues, merge requests etc by dropping unnecessary ORDER BY clause
+merge_request:
+author:
diff --git a/changelogs/unreleased/dm-emails-are-not-user-references.yml b/changelogs/unreleased/dm-emails-are-not-user-references.yml
deleted file mode 100644
index fe55a75a88fe94607847f9915f674b9975984d40..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/dm-emails-are-not-user-references.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Don't match email addresses or foo@bar as user references
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-empty-state-new-merge-request.yml b/changelogs/unreleased/dm-empty-state-new-merge-request.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5fad7a0f883dd296af38e7400b723a25d9568e0b
--- /dev/null
+++ b/changelogs/unreleased/dm-empty-state-new-merge-request.yml
@@ -0,0 +1,5 @@
+---
+title: Fix 'New merge request' button for users who don't have push access to canonical
+  project
+merge_request:
+author:
diff --git a/changelogs/unreleased/dm-encode-tree-and-blob-paths.yml b/changelogs/unreleased/dm-encode-tree-and-blob-paths.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c1a026e1f29bc03c85086298b19c5f145e66df58
--- /dev/null
+++ b/changelogs/unreleased/dm-encode-tree-and-blob-paths.yml
@@ -0,0 +1,5 @@
+---
+title: Fix issues with non-UTF8 filenames by always fixing the encoding of tree and
+  blob paths
+merge_request:
+author:
diff --git a/changelogs/unreleased/dm-fix-jump-button.yml b/changelogs/unreleased/dm-fix-jump-button.yml
deleted file mode 100644
index 4cde354fa28483765953fea28117da52c976f1d9..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/dm-fix-jump-button.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix title of discussion jump button at top of page
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-fix-parser-cache.yml b/changelogs/unreleased/dm-fix-parser-cache.yml
deleted file mode 100644
index 31c163b727227a471ef466973a0b77b84ab9755d..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/dm-fix-parser-cache.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Don't return nil for missing objects from parser cache
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-gitmodules-parsing.yml b/changelogs/unreleased/dm-gitmodules-parsing.yml
deleted file mode 100644
index a7d755d6c4d270fbb8e37975fb817d8703a27b03..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/dm-gitmodules-parsing.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Make .gitmodules parsing more resilient to syntax errors
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-gravatar-username.yml b/changelogs/unreleased/dm-gravatar-username.yml
deleted file mode 100644
index d50455061ec44a2691b321d7d72753bf1aaea4a4..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/dm-gravatar-username.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add username parameter to gravatar URL
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-group-page-name.yml b/changelogs/unreleased/dm-group-page-name.yml
new file mode 100644
index 0000000000000000000000000000000000000000..233879364e30e7d12ea000c09794a98835f176fe
--- /dev/null
+++ b/changelogs/unreleased/dm-group-page-name.yml
@@ -0,0 +1,4 @@
+---
+title: Show group name instead of path on group page
+merge_request:
+author:
diff --git a/changelogs/unreleased/dm-more-dependency-linkers.yml b/changelogs/unreleased/dm-more-dependency-linkers.yml
deleted file mode 100644
index 12d45e71e859b3fdb4e36b3ee014412f3a4e379b..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/dm-more-dependency-linkers.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Autolink package names in more dependency files
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-oauth-config-for.yml b/changelogs/unreleased/dm-oauth-config-for.yml
deleted file mode 100644
index 8fbbd45bb57f2bda25852ff68774f84e295b22ca..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/dm-oauth-config-for.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Return nil when looking up config for unknown LDAP provider
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-outdated-system-note.yml b/changelogs/unreleased/dm-outdated-system-note.yml
deleted file mode 100644
index a1038a1051b4c6527293366ec5573e2d7244cb30..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/dm-outdated-system-note.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add system note with link to diff comparison when MR discussion becomes outdated
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-page-image-size.yml b/changelogs/unreleased/dm-page-image-size.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b18c00470fcdaa68587326938bb19cfc831006c7
--- /dev/null
+++ b/changelogs/unreleased/dm-page-image-size.yml
@@ -0,0 +1,4 @@
+---
+title: Limit OpenGraph image size to 64x64
+merge_request:
+author:
diff --git a/changelogs/unreleased/dm-paste-code-inside-gfm-code.yml b/changelogs/unreleased/dm-paste-code-inside-gfm-code.yml
deleted file mode 100644
index d078ca449a574b3f7ec532bc9b8ddf151749062a..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/dm-paste-code-inside-gfm-code.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Don't wrap pasted code when it's already inside code tags
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-relative-submodule-url-trailing-whitespace.yml b/changelogs/unreleased/dm-relative-submodule-url-trailing-whitespace.yml
new file mode 100644
index 0000000000000000000000000000000000000000..616241dd9415fdb94c4fb7911cb22dd381da7334
--- /dev/null
+++ b/changelogs/unreleased/dm-relative-submodule-url-trailing-whitespace.yml
@@ -0,0 +1,4 @@
+---
+title: Strip trailing whitespace in relative submodule URL
+merge_request:
+author:
diff --git a/changelogs/unreleased/dm-revert-mr-8427.yml b/changelogs/unreleased/dm-revert-mr-8427.yml
deleted file mode 100644
index a91cff2e9cd7077a19d075f3a0260222e3996d2a..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/dm-revert-mr-8427.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Revert 'New file from interface on existing branch'
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-tree-last-commit.yml b/changelogs/unreleased/dm-tree-last-commit.yml
deleted file mode 100644
index 50619fd6ef2f59056fcecfa67bffe8b3e41e23df..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/dm-tree-last-commit.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Show last commit for current tree on tree page
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-unnecessary-top-padding.yml b/changelogs/unreleased/dm-unnecessary-top-padding.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4557c06f8e7f9a2bb61a728331bca7c4a36b4ed7
--- /dev/null
+++ b/changelogs/unreleased/dm-unnecessary-top-padding.yml
@@ -0,0 +1,4 @@
+---
+title: Remove unnecessary top padding on group MR index
+merge_request:
+author:
diff --git a/changelogs/unreleased/doc-gitaly-network.yml b/changelogs/unreleased/doc-gitaly-network.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5376d8d5096f9b7d0442828dc11fece0f66fcd4e
--- /dev/null
+++ b/changelogs/unreleased/doc-gitaly-network.yml
@@ -0,0 +1,4 @@
+---
+title: Add option to run Gitaly on a remote server
+merge_request: 12381
+author:
diff --git a/changelogs/unreleased/document-foreign-keys.yml b/changelogs/unreleased/document-foreign-keys.yml
deleted file mode 100644
index faa467e8185a36729a7942c3f936d7ce3b6734d1..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/document-foreign-keys.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add documentation about adding foreign keys
-merge_request:
-author:
diff --git a/changelogs/unreleased/dt-printing-to-api.yml b/changelogs/unreleased/dt-printing-to-api.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5253b57f21a81cd6e552a8ccb65201f83eebc377
--- /dev/null
+++ b/changelogs/unreleased/dt-printing-to-api.yml
@@ -0,0 +1,4 @@
+---
+title: Added printing_merge_requst_link_enabled to the API
+merge_request:
+author: David Turner <dturner@twosigma.com>
diff --git a/changelogs/unreleased/dturner-username.yml b/changelogs/unreleased/dturner-username.yml
deleted file mode 100644
index 09ba822ee658e768ee5ea42e1c931e80a8c6331d..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/dturner-username.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: add username field to push webhook
-merge_request:
-author: David Turner
diff --git a/changelogs/unreleased/dz-fix-submodule-subgroup.yml b/changelogs/unreleased/dz-fix-submodule-subgroup.yml
deleted file mode 100644
index 20c7c9ce657042f7b1f42cbdd40572bc4a38bc84..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/dz-fix-submodule-subgroup.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix submodule link to then project under subgroup
-merge_request: 11906
-author:
diff --git a/changelogs/unreleased/dz-project-list-cache-key.yml b/changelogs/unreleased/dz-project-list-cache-key.yml
deleted file mode 100644
index 9e4826e686a73d8f7a1bdabba7b438deaa62c2dd..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/dz-project-list-cache-key.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Use route.cache_key for project list cache key
-merge_request: 11325
-author:
diff --git a/changelogs/unreleased/dz-rename-pipelines-settings-tab.yml b/changelogs/unreleased/dz-rename-pipelines-settings-tab.yml
deleted file mode 100644
index 6a1232523bb451f3f38e0fe9b127c4f1671362c5..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/dz-rename-pipelines-settings-tab.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Rename CI/CD Pipelines to Pipelines in the project settings
-merge_request:
-author:
diff --git a/changelogs/unreleased/enable-auto-cancelling-by-default.yml b/changelogs/unreleased/enable-auto-cancelling-by-default.yml
deleted file mode 100644
index 8b1659bf38bbc3d73101d7018218d3deefd24520..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/enable-auto-cancelling-by-default.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Enable cancelling non-HEAD pending pipelines by default for all projects
-merge_request: 11023
-author:
diff --git a/changelogs/unreleased/enable-webpack-code-splitting.yml b/changelogs/unreleased/enable-webpack-code-splitting.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d61c3b97d11077dd49a6bc01831716e31d3d953e
--- /dev/null
+++ b/changelogs/unreleased/enable-webpack-code-splitting.yml
@@ -0,0 +1,5 @@
+---
+title: Enable support for webpack code-splitting by dynamically setting publicPath
+  at runtime
+merge_request: 12032
+author:
diff --git a/changelogs/unreleased/environment-detail-view.yml b/changelogs/unreleased/environment-detail-view.yml
deleted file mode 100644
index c74f70ea86ddd12351182969d29e938219bf3d26..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/environment-detail-view.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Make environment tables responsive
-merge_request:
-author:
diff --git a/changelogs/unreleased/expand-backlog-closed-lists-issue-boards.yml b/changelogs/unreleased/expand-backlog-closed-lists-issue-boards.yml
deleted file mode 100644
index 4796f8e918b03f718ea6c33b20de2e35768a1f2f..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/expand-backlog-closed-lists-issue-boards.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Expand/collapse backlog & closed lists in issue boards
-merge_request:
-author:
diff --git a/changelogs/unreleased/feature-flags-flipper.yml b/changelogs/unreleased/feature-flags-flipper.yml
deleted file mode 100644
index 5be5c44166d70d795d86e2e04cd7a4880bc8649c..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/feature-flags-flipper.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add feature toggles and API endpoints for admins
-merge_request: 11747
-author:
diff --git a/changelogs/unreleased/feature-gb-persist-pipeline-stages.yml b/changelogs/unreleased/feature-gb-persist-pipeline-stages.yml
deleted file mode 100644
index 1404b3423594f26a10c652bed222515539fc41a1..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/feature-gb-persist-pipeline-stages.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Persist pipeline stages in the database
-merge_request: 11790
-author:
diff --git a/changelogs/unreleased/feature-no-hypen-at-end-of-commit-ref-slug.yml b/changelogs/unreleased/feature-no-hypen-at-end-of-commit-ref-slug.yml
new file mode 100644
index 0000000000000000000000000000000000000000..bbcf2946ea79cdf88549502bfc5896798246d635
--- /dev/null
+++ b/changelogs/unreleased/feature-no-hypen-at-end-of-commit-ref-slug.yml
@@ -0,0 +1,4 @@
+---
+title: Omit trailing / leading hyphens in CI_COMMIT_REF_SLUG variable to make it usable as a hostname
+merge_request: 11218
+author: Stefan Hanreich
diff --git a/changelogs/unreleased/feature-print-go-version-in-env-info.yml b/changelogs/unreleased/feature-print-go-version-in-env-info.yml
deleted file mode 100644
index 34c19b06edac2c9ab1091789abdc0ceec901d429..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/feature-print-go-version-in-env-info.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Print Go version in rake gitlab:env:info
-merge_request: 11241
-author:
diff --git a/changelogs/unreleased/feature-rss-scoped-token.yml b/changelogs/unreleased/feature-rss-scoped-token.yml
deleted file mode 100644
index 740d8778be2622b25603d5ad519b32f900b1c81a..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/feature-rss-scoped-token.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Expose atom links with an RSS token instead of using the private token
-merge_request: 11647
-author: Alexis Reigel
diff --git a/changelogs/unreleased/fix-33991.yml b/changelogs/unreleased/fix-33991.yml
new file mode 100644
index 0000000000000000000000000000000000000000..39732611b6e736d7174f503a5d1d7f12051168c8
--- /dev/null
+++ b/changelogs/unreleased/fix-33991.yml
@@ -0,0 +1,4 @@
+---
+title: Users can subscribe to group labels on the group labels page
+merge_request:
+author:
diff --git a/changelogs/unreleased/fix-assigned-issuable-lists.yml b/changelogs/unreleased/fix-assigned-issuable-lists.yml
new file mode 100644
index 0000000000000000000000000000000000000000..fc2cd18ddb6b870b28138cd3327d26a6d0185dcb
--- /dev/null
+++ b/changelogs/unreleased/fix-assigned-issuable-lists.yml
@@ -0,0 +1,5 @@
+---
+title: Add issuable-list class to shared mr/issue lists to fix new responsive layout
+  design
+merge_request:
+author:
diff --git a/changelogs/unreleased/fix-counter-cache-for-acts-as-taggable.yml b/changelogs/unreleased/fix-counter-cache-for-acts-as-taggable.yml
deleted file mode 100644
index e40668546c0d05b33d898995cf499dcbabdf748a..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fix-counter-cache-for-acts-as-taggable.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix counter cache for acts as taggable
-merge_request:
-author:
diff --git a/changelogs/unreleased/fix-encoding-binary-issue.yml b/changelogs/unreleased/fix-encoding-binary-issue.yml
deleted file mode 100644
index ac9aff64a88e46161e9717b2d1b05458380431af..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fix-encoding-binary-issue.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix binary encoding error on MR diffs
-merge_request: 11929
-author:
diff --git a/changelogs/unreleased/fix-gb-exclude-manual-actions-from-cancelable-jobs.yml b/changelogs/unreleased/fix-gb-exclude-manual-actions-from-cancelable-jobs.yml
deleted file mode 100644
index a16fc775b5e3039ea1a691373e21e46d193173fd..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fix-gb-exclude-manual-actions-from-cancelable-jobs.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Exclude manual actions when checking if pipeline can be canceled
-merge_request: 11562
-author:
diff --git a/changelogs/unreleased/fix-gb-fix-skipped-pipeline-with-allowed-to-fail-jobs.yml b/changelogs/unreleased/fix-gb-fix-skipped-pipeline-with-allowed-to-fail-jobs.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f59c6ecd90c59278028b6a324ee8546008254ed3
--- /dev/null
+++ b/changelogs/unreleased/fix-gb-fix-skipped-pipeline-with-allowed-to-fail-jobs.yml
@@ -0,0 +1,4 @@
+---
+title: Fix CI/CD status in case there are only allowed to failed jobs in the pipeline
+merge_request: 11166
+author:
diff --git a/changelogs/unreleased/fix-github-clone-wiki.yml b/changelogs/unreleased/fix-github-clone-wiki.yml
deleted file mode 100644
index eadd90e13901220a69edc8826f256af5652ad385..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fix-github-clone-wiki.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Github - Fix token interpolation when cloning wiki repository
-merge_request:
-author:
diff --git a/changelogs/unreleased/fix-github-import.yml b/changelogs/unreleased/fix-github-import.yml
deleted file mode 100644
index 3a57152f7a82b5ae1e88603564289d3593312732..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fix-github-import.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix token interpolation when setting the Github remote
-merge_request:
-author:
diff --git a/changelogs/unreleased/fix-n-plus-one-queries-for-user-access.yml b/changelogs/unreleased/fix-n-plus-one-queries-for-user-access.yml
deleted file mode 100644
index c2671a96b834a7b277ddf59b40de820ae4ae98f6..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fix-n-plus-one-queries-for-user-access.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix N+1 queries for non-members in comment threads
-merge_request:
-author:
diff --git a/changelogs/unreleased/fix-overflow-slash-commands.yml b/changelogs/unreleased/fix-overflow-slash-commands.yml
new file mode 100644
index 0000000000000000000000000000000000000000..98ec399e8cb8e2569afa34c374c80f266008e50d
--- /dev/null
+++ b/changelogs/unreleased/fix-overflow-slash-commands.yml
@@ -0,0 +1,4 @@
+---
+title: Fixed overflow on mobile screens for the slash commands
+merge_request:
+author:
diff --git a/changelogs/unreleased/fix-sidebar-showing-mobile-merge-requests.yml b/changelogs/unreleased/fix-sidebar-showing-mobile-merge-requests.yml
new file mode 100644
index 0000000000000000000000000000000000000000..856990a6126642b54f81b517e13b1d84761868ab
--- /dev/null
+++ b/changelogs/unreleased/fix-sidebar-showing-mobile-merge-requests.yml
@@ -0,0 +1,4 @@
+---
+title: Fixed sidebar not collapsing on merge requests in mobile screens
+merge_request:
+author:
diff --git a/changelogs/unreleased/fix-support-for-external-ci-services.yml b/changelogs/unreleased/fix-support-for-external-ci-services.yml
deleted file mode 100644
index eecb451925995c5569a446dd31dae9a46fab83f9..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fix-support-for-external-ci-services.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix support for external CI services
-merge_request: 11176
-author:
diff --git a/changelogs/unreleased/fix_commits_page.yml b/changelogs/unreleased/fix_commits_page.yml
deleted file mode 100644
index a2afaf6e6264d99348cc79b7c0db2d4819f17ae8..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fix_commits_page.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix duplication of commits header on commits page
-merge_request: 11006
-author: @blackst0ne
diff --git a/changelogs/unreleased/fix_diff_line_comments.yml b/changelogs/unreleased/fix_diff_line_comments.yml
deleted file mode 100644
index bdb0539b49df78c4a632d37aed14bbb868a44959..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fix_diff_line_comments.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'Fix: A diff comment on a change at last line of a file shows as two comments
-  in discussion'
-merge_request:
-author:
diff --git a/changelogs/unreleased/fixed-confidential-issue-bar.yml b/changelogs/unreleased/fixed-confidential-issue-bar.yml
deleted file mode 100644
index 6a41590d0af15db4b118a2432164bfd46ab07487..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fixed-confidential-issue-bar.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Make confidential issues more obviously confidential
-merge_request:
-author:
diff --git a/changelogs/unreleased/gitaly-local-branches.yml b/changelogs/unreleased/gitaly-local-branches.yml
deleted file mode 100644
index adcc0fa6280f792f81c5a006feea82cc3ad3b5fd..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/gitaly-local-branches.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add suport for find_local_branches GRPC from Gitaly
-merge_request: 10059
-author:
diff --git a/changelogs/unreleased/gitaly-opt-out.yml b/changelogs/unreleased/gitaly-opt-out.yml
deleted file mode 100644
index 2f89e0bfc9ac271f238bfac37f4008ecdbd9d3ce..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/gitaly-opt-out.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Enable Gitaly by default in installations from source
-merge_request: 11796
-author:
diff --git a/changelogs/unreleased/hb-fix-abuse-report-on-stale-user-profile.yml b/changelogs/unreleased/hb-fix-abuse-report-on-stale-user-profile.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ec2f4f9c3d8b97d27d4b046fd00cf51fa671c487
--- /dev/null
+++ b/changelogs/unreleased/hb-fix-abuse-report-on-stale-user-profile.yml
@@ -0,0 +1,4 @@
+---
+title: Fix errors caused by attempts to report already blocked or deleted users
+merge_request: 12502
+author: Horacio Bertorello
diff --git a/changelogs/unreleased/hb-hide-archived-labels-from-group-issue-tracker.yml b/changelogs/unreleased/hb-hide-archived-labels-from-group-issue-tracker.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3b465d8412673e4aa6aa83962e0a6c425f7f634b
--- /dev/null
+++ b/changelogs/unreleased/hb-hide-archived-labels-from-group-issue-tracker.yml
@@ -0,0 +1,4 @@
+---
+title: Hide archived project labels from group issue tracker
+merge_request: 12547
+author: Horacio Bertorello
diff --git a/changelogs/unreleased/introduce-source-to-pipelines.yml b/changelogs/unreleased/introduce-source-to-pipelines.yml
deleted file mode 100644
index 7898bd31b39a252fbef2cbeff1183e128ed69b57..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/introduce-source-to-pipelines.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Introduce source to Pipeline entity
-merge_request:
-author:
diff --git a/changelogs/unreleased/issuable-form-create-label-sub-groups.yml b/changelogs/unreleased/issuable-form-create-label-sub-groups.yml
deleted file mode 100644
index 54b818d6d5e34e23fbf2baa37908d9de14cb7e45..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/issuable-form-create-label-sub-groups.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixed create new label form in issue form not working for sub-group projects
-merge_request:
-author:
diff --git a/changelogs/unreleased/issue-23254.yml b/changelogs/unreleased/issue-23254.yml
deleted file mode 100644
index 568a7a41c3038d67c21ffcf59743f1ecb2190609..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/issue-23254.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixed style on unsubscribe page
-merge_request: 
-author: Gustav Ernberg
diff --git a/changelogs/unreleased/issue-edit-inline.yml b/changelogs/unreleased/issue-edit-inline.yml
deleted file mode 100644
index db03d1bdac41b16783d67575cfd402050c69e0a5..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/issue-edit-inline.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Enables inline editing for an issues title & description
-merge_request:
-author:
diff --git a/changelogs/unreleased/issue-template-reproduce-in-example-project.yml b/changelogs/unreleased/issue-template-reproduce-in-example-project.yml
deleted file mode 100644
index 8116007b45995ef35778fce205d46e7212735ff0..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/issue-template-reproduce-in-example-project.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Ask for an example project for bug reports
-merge_request:
-author:
diff --git a/changelogs/unreleased/issue-templates-summary-lines.yml b/changelogs/unreleased/issue-templates-summary-lines.yml
deleted file mode 100644
index 0c8c3d884cebe588b15a6e916d511a05cdd27072..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/issue-templates-summary-lines.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add summary lines for collapsed details in the bug report template
-merge_request:
-author:
diff --git a/changelogs/unreleased/issue_19262.yml b/changelogs/unreleased/issue_19262.yml
deleted file mode 100644
index 7bcbc647fcbeb4051793269ffb4e780a167a3431..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/issue_19262.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Prevent commits from upstream repositories to be re-processed by forks
-merge_request:
-author:
diff --git a/changelogs/unreleased/issue_20900.yml b/changelogs/unreleased/issue_20900.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e8cef6d2bce8aac58fcb5d3cf6704d6911672038
--- /dev/null
+++ b/changelogs/unreleased/issue_20900.yml
@@ -0,0 +1,4 @@
+---
+title: Remove issues/merge requests drag n drop and sorting from milestone view
+merge_request:
+author:
diff --git a/changelogs/unreleased/issue_27166_2.yml b/changelogs/unreleased/issue_27166_2.yml
deleted file mode 100644
index 9b9906e03dda31f3e96c381b4908878975e4102c..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/issue_27166_2.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Avoid repeated queries for pipeline builds on merge requests
-merge_request:
-author:
diff --git a/changelogs/unreleased/issue_27168_2.yml b/changelogs/unreleased/issue_27168_2.yml
deleted file mode 100644
index c67692493e0c4c5486c2d2b3c5ba7b1ea570b779..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/issue_27168_2.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Preloads head pipeline for merge request collection
-merge_request:
-author:
diff --git a/changelogs/unreleased/issue_32225_2.yml b/changelogs/unreleased/issue_32225_2.yml
deleted file mode 100644
index 320b9fe00b8a1102d37cd5dabd6d283be8a3a8bd..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/issue_32225_2.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Handle head pipeline when creating merge requests
-merge_request:
-author:
diff --git a/changelogs/unreleased/issue_33205.yml b/changelogs/unreleased/issue_33205.yml
new file mode 100644
index 0000000000000000000000000000000000000000..54b442048d8926ef673cdfe33ecc027f3bc02cc4
--- /dev/null
+++ b/changelogs/unreleased/issue_33205.yml
@@ -0,0 +1,4 @@
+---
+title: Fix API bug accepting wrong parameter to create merge request
+merge_request:
+author:
diff --git a/changelogs/unreleased/issueable-list-cleanup.yml b/changelogs/unreleased/issueable-list-cleanup.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d3d67d04574b6e33d3d537364e8ae3ebb9b6acce
--- /dev/null
+++ b/changelogs/unreleased/issueable-list-cleanup.yml
@@ -0,0 +1,4 @@
+---
+title: Clean up UI of issuable lists and make more responsive
+merge_request:
+author:
diff --git a/changelogs/unreleased/jouve-gitlab-ce-admin_keys.yml b/changelogs/unreleased/jouve-gitlab-ce-admin_keys.yml
deleted file mode 100644
index df4de9f4e218b262f2aaf8ad1428c7f3f3c159ef..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/jouve-gitlab-ce-admin_keys.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Redirect to user's keys index instead of user's index after a key is deleted
-  in the admin
-merge_request: 10227
-author: Cyril Jouve
diff --git a/changelogs/unreleased/mabes-gitlab-ce-bypass-auto-login.yml b/changelogs/unreleased/mabes-gitlab-ce-bypass-auto-login.yml
deleted file mode 100644
index a321ed9d7d854810e8f35af6e3aeffe0c3b9ca12..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/mabes-gitlab-ce-bypass-auto-login.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Allow manual bypass of auto_sign_in_with_provider with a new param
-merge_request: 10187
-author: Maxime Besson
diff --git a/changelogs/unreleased/migrate-artifacts-to-a-new-path.yml b/changelogs/unreleased/migrate-artifacts-to-a-new-path.yml
deleted file mode 100644
index bd022a3a91bd913e0a6bb4b3086add07c81df5ea..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/migrate-artifacts-to-a-new-path.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Migrate artifacts to a new path
-merge_request:
-author:
diff --git a/changelogs/unreleased/mk-fix-git-over-http-rejections.yml b/changelogs/unreleased/mk-fix-git-over-http-rejections.yml
deleted file mode 100644
index e75740e913f9b9319820e3539099e93b587dcbc0..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/mk-fix-git-over-http-rejections.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix Git-over-HTTP error statuses and improve error messages
-merge_request: 11398
-author:
diff --git a/changelogs/unreleased/monitoring-dashboard-fine-tuning-ux.yml b/changelogs/unreleased/monitoring-dashboard-fine-tuning-ux.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f84d41b792939696e3df8c2c97f34e2b8fc24260
--- /dev/null
+++ b/changelogs/unreleased/monitoring-dashboard-fine-tuning-ux.yml
@@ -0,0 +1,4 @@
+---
+title: Improve the overall UX for the new monitoring dashboard
+merge_request:
+author:
diff --git a/changelogs/unreleased/monitoring-dashboard-fix-y-label.yml b/changelogs/unreleased/monitoring-dashboard-fix-y-label.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8a0e9ca855c682b12db627f1a4ecadf923d06e9f
--- /dev/null
+++ b/changelogs/unreleased/monitoring-dashboard-fix-y-label.yml
@@ -0,0 +1,4 @@
+---
+title: Fixed the y_label not setting correctly for each graph on the monitoring dashboard
+merge_request:
+author:
diff --git a/changelogs/unreleased/moved-submodules.yml b/changelogs/unreleased/moved-submodules.yml
new file mode 100644
index 0000000000000000000000000000000000000000..eee858717ed7934e05f8ad176c77fc2da7d2f83c
--- /dev/null
+++ b/changelogs/unreleased/moved-submodules.yml
@@ -0,0 +1,4 @@
+---
+title: 'Handle renamed submodules in repository browser'
+merge_request: 10798
+author: David Turner
diff --git a/changelogs/unreleased/mr-widget-memory-usage-tech-debt-fix.yml b/changelogs/unreleased/mr-widget-memory-usage-tech-debt-fix.yml
new file mode 100644
index 0000000000000000000000000000000000000000..14b5493a2466269b20b5a9a6b4e72f5c6c67ee89
--- /dev/null
+++ b/changelogs/unreleased/mr-widget-memory-usage-tech-debt-fix.yml
@@ -0,0 +1,4 @@
+---
+title: Changed utilities imports from ~ to relative paths
+merge_request:
+author:
diff --git a/changelogs/unreleased/mrchrisw-catch-openssl.yml b/changelogs/unreleased/mrchrisw-catch-openssl.yml
deleted file mode 100644
index a8b433fb0cdda527b7f6be9a6798cf0d30311843..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/mrchrisw-catch-openssl.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Rescue OpenSSL::SSL::SSLError in JiraService & IssueTrackerService
-merge_request:
-author:
diff --git a/changelogs/unreleased/omega-submodules.yml b/changelogs/unreleased/omega-submodules.yml
deleted file mode 100644
index 1488eb72174b3d2cc5dd37b722700c94e6b6baf9..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/omega-submodules.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: 'Repository browser: handle in-repository submodule urls'
-merge_request:
-author: David Turner
diff --git a/changelogs/unreleased/pat-alert-when-signin-disabled.yml b/changelogs/unreleased/pat-alert-when-signin-disabled.yml
new file mode 100644
index 0000000000000000000000000000000000000000..dca3670aeb7b961c59a2fdb2a852dbd1ccb4251f
--- /dev/null
+++ b/changelogs/unreleased/pat-alert-when-signin-disabled.yml
@@ -0,0 +1,4 @@
+---
+title: Provide hint to create a personal access token for Git over HTTP
+merge_request: 12105
+author: Robin Bobbitt
diff --git a/changelogs/unreleased/polish-sidebar-toggle.yml b/changelogs/unreleased/polish-sidebar-toggle.yml
new file mode 100644
index 0000000000000000000000000000000000000000..41ec567fc5254bdbce2469f0679ca18f76ff0580
--- /dev/null
+++ b/changelogs/unreleased/polish-sidebar-toggle.yml
@@ -0,0 +1,4 @@
+---
+title: Remove unused space in sidebar todo toggle when not signed in
+merge_request:
+author:
diff --git a/changelogs/unreleased/prevent-project-transfer.yml b/changelogs/unreleased/prevent-project-transfer.yml
deleted file mode 100644
index a5c74676aab2fc862943ceca12a499b851c17a0f..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/prevent-project-transfer.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Prevent project transfers if a new group is not selected
-merge_request:
-author:
diff --git a/changelogs/unreleased/project-readme-limited-width.yml b/changelogs/unreleased/project-readme-limited-width.yml
new file mode 100644
index 0000000000000000000000000000000000000000..17d87a5691e8ebcb6854ca09b15fa46336098b57
--- /dev/null
+++ b/changelogs/unreleased/project-readme-limited-width.yml
@@ -0,0 +1,4 @@
+---
+title: Limit the width of the projects README text
+merge_request:
+author:
diff --git a/changelogs/unreleased/projects-api-import-status.yml b/changelogs/unreleased/projects-api-import-status.yml
deleted file mode 100644
index 06603c0adec8d77ae2b0874422599a6d737110d2..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/projects-api-import-status.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Expose import_status in Projects API
-merge_request: 11851
-author: Robin Bobbitt
diff --git a/changelogs/unreleased/protected-branches-no-one-merge.yml b/changelogs/unreleased/protected-branches-no-one-merge.yml
deleted file mode 100644
index 52d93793f3dacfa63b99866640ade5b6af7ea89b..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/protected-branches-no-one-merge.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Allow 'no one' as an option for allowed to merge on a procted branch
-merge_request:
-author:
diff --git a/changelogs/unreleased/remove-old-isobject.yml b/changelogs/unreleased/remove-old-isobject.yml
deleted file mode 100644
index 67b186422539879b7036c7f95dcb3a60982209ea..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/remove-old-isobject.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Remove unused code and uses underscore
-merge_request:
-author:
diff --git a/changelogs/unreleased/rename-builds-controller.yml b/changelogs/unreleased/rename-builds-controller.yml
deleted file mode 100644
index 7f6872ccf953a4c5aef20385dfabacc4be4633e5..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/rename-builds-controller.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Change /builds in the URL to /-/jobs. Backward URLs were also added
-merge_request: 11407
-author:
diff --git a/changelogs/unreleased/replace_spinach_spec_profile_notifications-feature.yml b/changelogs/unreleased/replace_spinach_spec_profile_notifications-feature.yml
new file mode 100644
index 0000000000000000000000000000000000000000..38227ebfa7a3688155f167f9ebe3bcaf104d86b7
--- /dev/null
+++ b/changelogs/unreleased/replace_spinach_spec_profile_notifications-feature.yml
@@ -0,0 +1,4 @@
+---
+title: Replace 'profile/notifications.feature' spinach test with an rspec analog
+merge_request: 12345
+author: @blackst0ne
diff --git a/changelogs/unreleased/replase_spinach_spec_create-feature.yml b/changelogs/unreleased/replase_spinach_spec_create-feature.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0613d195d56638b48cb556499c42d31e839a9d98
--- /dev/null
+++ b/changelogs/unreleased/replase_spinach_spec_create-feature.yml
@@ -0,0 +1,4 @@
+---
+title: Replace 'create.feature' spinach test with an rspec analog
+merge_request: 12343
+author: @blackst0ne
diff --git a/changelogs/unreleased/rework-authorizations-performance.yml b/changelogs/unreleased/rework-authorizations-performance.yml
deleted file mode 100644
index f64257a6f56c65301cda516f5f987ddf505f5b31..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/rework-authorizations-performance.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: >
-  Project authorizations are calculated much faster when using PostgreSQL, and
-  nested groups support for MySQL has been removed
-merge_request: 10885
-author:
diff --git a/changelogs/unreleased/search-restrict-projects-to-group.yml b/changelogs/unreleased/search-restrict-projects-to-group.yml
deleted file mode 100644
index ac134bc5bcea7df70e079460b5840691e09efeb1..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/search-restrict-projects-to-group.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Restricts search projects dropdown to group projects when group is selected
-merge_request:
-author:
diff --git a/changelogs/unreleased/sh-allow-force-repo-create.yml b/changelogs/unreleased/sh-allow-force-repo-create.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2a65ba807bb637d97a335b9090798c10e16c8d02
--- /dev/null
+++ b/changelogs/unreleased/sh-allow-force-repo-create.yml
@@ -0,0 +1,4 @@
+---
+title: Make Project#ensure_repository force create a repo
+merge_request:
+author:
diff --git a/changelogs/unreleased/sh-fix-container-registry-s3-redirects.yml b/changelogs/unreleased/sh-fix-container-registry-s3-redirects.yml
deleted file mode 100644
index 1e783811b66171681fff3e7920a08c06b6594f11..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/sh-fix-container-registry-s3-redirects.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Properly handle container registry redirects to fix metadata stored on a S3 backend
-merge_request:
-author:
diff --git a/changelogs/unreleased/sh-fix-project-destroy-in-namespace.yml b/changelogs/unreleased/sh-fix-project-destroy-in-namespace.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9309f961345f419e1c8fa61213eaee978ff37c72
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-project-destroy-in-namespace.yml
@@ -0,0 +1,4 @@
+---
+title: Defer project destroys within a namespace in Groups::DestroyService#async_execute
+merge_request:
+author:
diff --git a/changelogs/unreleased/sh-fix-refactor-uploader-work-dir.yml b/changelogs/unreleased/sh-fix-refactor-uploader-work-dir.yml
deleted file mode 100644
index 255608bd89a61f6570a3e4703e797d3ec32affc4..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/sh-fix-refactor-uploader-work-dir.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Set artifact working directory to be in the destination store to prevent unnecessary I/O
-merge_request:
-author:
diff --git a/changelogs/unreleased/sh-log-application-controller-exceptions-sentry.yml b/changelogs/unreleased/sh-log-application-controller-exceptions-sentry.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ec9ceab3d81cb56f1c2d855eeccbe6a9d2d5f192
--- /dev/null
+++ b/changelogs/unreleased/sh-log-application-controller-exceptions-sentry.yml
@@ -0,0 +1,4 @@
+---
+title: Log rescued exceptions to Sentry
+merge_request:
+author:
diff --git a/changelogs/unreleased/sh-optimize-project-commit-api.yml b/changelogs/unreleased/sh-optimize-project-commit-api.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e6a8a80593c805ddf3c75b25506b52da6d2dab1d
--- /dev/null
+++ b/changelogs/unreleased/sh-optimize-project-commit-api.yml
@@ -0,0 +1,4 @@
+---
+title: Optimize creation of commit API by using Repository#commit instead of Repository#commits
+merge_request:
+author:
diff --git a/changelogs/unreleased/speed-up-issue-counting-for-a-project.yml b/changelogs/unreleased/speed-up-issue-counting-for-a-project.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6bf03d9a3820d86631050244d7c6c26f9633dcdf
--- /dev/null
+++ b/changelogs/unreleased/speed-up-issue-counting-for-a-project.yml
@@ -0,0 +1,5 @@
+---
+title: Cache open issue and merge request counts for project tabs to speed up project
+  pages
+merge_request: 12457
+author:
diff --git a/changelogs/unreleased/stop-notification-recipient-service-modifying-participants.yml b/changelogs/unreleased/stop-notification-recipient-service-modifying-participants.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7e66ea4ca8bb3bbbf78224ada39e1de34d75c391
--- /dev/null
+++ b/changelogs/unreleased/stop-notification-recipient-service-modifying-participants.yml
@@ -0,0 +1,5 @@
+---
+title: Ensure participants for issues, merge requests, etc. are calculated correctly
+  when sending notifications
+merge_request:
+author:
diff --git a/changelogs/unreleased/sync-email-from-omniauth.yml b/changelogs/unreleased/sync-email-from-omniauth.yml
deleted file mode 100644
index ed14a95a5f11f6b513fa5fe7cdc7de63673fc860..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/sync-email-from-omniauth.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Sync email address from specified omniauth provider
-merge_request: 11268
-author: Robin Bobbitt
diff --git a/changelogs/unreleased/task-list-2.yml b/changelogs/unreleased/task-list-2.yml
deleted file mode 100644
index cbae8178081531f1bf42fef1739fe1be332442bf..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/task-list-2.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Update task_list to version 2.0.0
-merge_request: 11525
-author: Jared Deckard <jared.deckard@gmail.com>
diff --git a/changelogs/unreleased/tc-cache-trackable-attributes.yml b/changelogs/unreleased/tc-cache-trackable-attributes.yml
deleted file mode 100644
index 4a2cf50893a5dae16ddcdffe9958d81295cc3ba4..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/tc-cache-trackable-attributes.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: "Limit User's trackable attributes, like `current_sign_in_at`, to update at most once/hour"
-merge_request: 11053
-author:
diff --git a/changelogs/unreleased/tc-clean-pending-delete-projects.yml b/changelogs/unreleased/tc-clean-pending-delete-projects.yml
deleted file mode 100644
index 31b43999c314146246c2c01dc98d3965a82bf6a6..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/tc-clean-pending-delete-projects.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add post-deploy migration to clean up projects in `pending_delete` state
-merge_request: 11044
-author:
diff --git a/changelogs/unreleased/tc-improve-project-api-perf.yml b/changelogs/unreleased/tc-improve-project-api-perf.yml
deleted file mode 100644
index 7e88466c05843b28076c87b9a8e0266f382b1319..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/tc-improve-project-api-perf.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Improve performance of ProjectFinder used in /projects API endpoint
-merge_request: 11666
-author:
diff --git a/changelogs/unreleased/tc-refactor-projects-finder-init-collection.yml b/changelogs/unreleased/tc-refactor-projects-finder-init-collection.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7bcbd6468c79204bb82edeafcf2273c93db1656e
--- /dev/null
+++ b/changelogs/unreleased/tc-refactor-projects-finder-init-collection.yml
@@ -0,0 +1,4 @@
+---
+title: Add User#full_private_access? to check if user has access to all private groups & projects
+merge_request: 12373
+author:
diff --git a/changelogs/unreleased/up-arrow-focus-discussion-comment.yml b/changelogs/unreleased/up-arrow-focus-discussion-comment.yml
deleted file mode 100644
index 5457dab6d3d35d113603d11373d59f5ade29bbaf..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/up-arrow-focus-discussion-comment.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix up arrow not editing last discussion comment
-merge_request:
-author:
diff --git a/changelogs/unreleased/update-admin-health-page.yml b/changelogs/unreleased/update-admin-health-page.yml
deleted file mode 100644
index 51aa6682b49c2450d3bed6e2c620193efabf9ef1..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/update-admin-health-page.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Added application readiness endpoints to the monitoring health check admin
-  view
-merge_request:
-author:
diff --git a/changelogs/unreleased/update_bootsnap_1-1-1.yml b/changelogs/unreleased/update_bootsnap_1-1-1.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9ecfe4b60c8501bdac856f6a26256d75d458dcb2
--- /dev/null
+++ b/changelogs/unreleased/update_bootsnap_1-1-1.yml
@@ -0,0 +1,4 @@
+---
+title: Bump bootsnap to 1.1.1
+merge_request: 12425
+author: @blackst0ne
diff --git a/changelogs/unreleased/use_relative_path_for_project_avatars.yml b/changelogs/unreleased/use_relative_path_for_project_avatars.yml
deleted file mode 100644
index e3d0c0e11870b1213ed2394a79abdfde2191c8aa..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/use_relative_path_for_project_avatars.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Use relative paths for group/project/user avatars
-merge_request: 11001
-author: blackst0ne
diff --git a/changelogs/unreleased/wait-for-ajax-handling-all-js-requests.yml b/changelogs/unreleased/wait-for-ajax-handling-all-js-requests.yml
deleted file mode 100644
index 14aebe792c2f0fac1193c9ae858a926d8a3dacd9..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/wait-for-ajax-handling-all-js-requests.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Use wait_for_requests for both ajax and Vue requests
-merge_request:
-author:
diff --git a/changelogs/unreleased/winh-current-user-filter.yml b/changelogs/unreleased/winh-current-user-filter.yml
deleted file mode 100644
index e5409827b31e605a0255dcadd2dc135af9eabda3..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/winh-current-user-filter.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Show current user immediately in issuable filters
-merge_request: 11630
-author:
diff --git a/changelogs/unreleased/winh-pipeline-author-link.yml b/changelogs/unreleased/winh-pipeline-author-link.yml
deleted file mode 100644
index 1b903d1e35714b6f77d301230b08a166ac5b255c..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/winh-pipeline-author-link.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Link to commit author user page from pipelines
-merge_request: 11100
-author:
diff --git a/changelogs/unreleased/winh-styled-people-search-bar.yml b/changelogs/unreleased/winh-styled-people-search-bar.yml
deleted file mode 100644
index a088af37d8d6772e1f81f66b520ca480c6c9a9e9..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/winh-styled-people-search-bar.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Style people in issuable search bar
-merge_request: 11402
-author:
diff --git a/changelogs/unreleased/zj-clean-up-ci-variables-table.yml b/changelogs/unreleased/zj-clean-up-ci-variables-table.yml
deleted file mode 100644
index ea2db40d590351eb3d38f0d46a22a84bd95839a5..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/zj-clean-up-ci-variables-table.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Cleanup ci_variables schema and table
-merge_request:
-author:
diff --git a/changelogs/unreleased/zj-faster-charts-page.yml b/changelogs/unreleased/zj-faster-charts-page.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9afcf1113282c1884ad6723cef4a495ef6a2872a
--- /dev/null
+++ b/changelogs/unreleased/zj-faster-charts-page.yml
@@ -0,0 +1,4 @@
+---
+title: Improve performance of the pipeline charts page
+merge_request: 12378
+author:
diff --git a/changelogs/unreleased/zj-i18n-pipeline-schedules.yml b/changelogs/unreleased/zj-i18n-pipeline-schedules.yml
deleted file mode 100644
index 51c82a163594038aca30eac9625073725ff54fdc..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/zj-i18n-pipeline-schedules.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Allow translation of Pipeline Schedules
-merge_request:
-author:
diff --git a/changelogs/unreleased/zj-job-view-goes-real-time.yml b/changelogs/unreleased/zj-job-view-goes-real-time.yml
deleted file mode 100644
index 376c9dfa65fb9004c28ebd30a296070b683870ee..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/zj-job-view-goes-real-time.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Job details page update real time
-merge_request: 11651
-author:
diff --git a/changelogs/unreleased/zj-pipeline-schedule-owner.yml b/changelogs/unreleased/zj-pipeline-schedule-owner.yml
deleted file mode 100644
index be704e173abba3b073425d41df88f82d04e12a2d..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/zj-pipeline-schedule-owner.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add foreign key for pipeline schedule owner
-merge_request: 11233
-author:
diff --git a/changelogs/unreleased/zj-prom-pipeline-count.yml b/changelogs/unreleased/zj-prom-pipeline-count.yml
deleted file mode 100644
index 191e4f2f949c3adedd9919ac1b5da3e74745f66d..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/zj-prom-pipeline-count.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add prometheus metrics on pipeline creation
-merge_request:
-author:
diff --git a/changelogs/unreleased/zj-raise-etag-route-regex-miss.yml b/changelogs/unreleased/zj-raise-etag-route-regex-miss.yml
deleted file mode 100644
index 57a5f4e44c0ff92d4fd4a08cd07081bd30b04aa6..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/zj-raise-etag-route-regex-miss.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix etag route not being a match for environments
-merge_request:
-author:
diff --git a/changelogs/unreleased/zj-read-registry-pat.yml b/changelogs/unreleased/zj-read-registry-pat.yml
deleted file mode 100644
index d36159bbdf59020d137880822952d23e313836ec..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/zj-read-registry-pat.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Allow pulling of container images using personal access tokens
-merge_request: 11845
-author:
diff --git a/changelogs/unreleased/zj-realtime-env-list.yml b/changelogs/unreleased/zj-realtime-env-list.yml
deleted file mode 100644
index 6460d17edc930802810ae82b664de5e3bc19150f..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/zj-realtime-env-list.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Make environment table realtime
-merge_request: 11333
-author:
diff --git a/changelogs/unreleased/zj-review-apps-usage-data.yml b/changelogs/unreleased/zj-review-apps-usage-data.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7d224d0fc32e649e147e6201ec592147ebec1fc1
--- /dev/null
+++ b/changelogs/unreleased/zj-review-apps-usage-data.yml
@@ -0,0 +1,4 @@
+---
+title: Add review apps to usage metrics
+merge_request: 12185
+author:
diff --git a/changelogs/unreleased/zj-sort-env-folders.yml b/changelogs/unreleased/zj-sort-env-folders.yml
deleted file mode 100644
index b3ca97aef945190c0af91f58a57ec2a3a7cbb6d3..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/zj-sort-env-folders.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Sort folder for environments
-merge_request:
-author:
diff --git a/changelogs/unreleased/zj-usage-ping-only-gl-pipelines.yml b/changelogs/unreleased/zj-usage-ping-only-gl-pipelines.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0ace7b996573bb053419c6073796904474902e03
--- /dev/null
+++ b/changelogs/unreleased/zj-usage-ping-only-gl-pipelines.yml
@@ -0,0 +1,4 @@
+---
+title: Split pipelines as internal and external in the usage data
+merge_request: 12277
+author:
diff --git a/config/application.rb b/config/application.rb
index 8bbecf3ed0f5287cd2b45e62406b5ea7f27a6526..a9a961d75206bc11ddb255b4ea018134d81189fb 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -109,6 +109,8 @@ module Gitlab
     config.assets.precompile << "lib/ace.js"
     config.assets.precompile << "vendor/assets/fonts/*"
     config.assets.precompile << "test.css"
+    config.assets.precompile << "new_nav.css"
+    config.assets.precompile << "new_sidebar.css"
 
     # Version of your assets, change this if you want to expire all your assets
     config.assets.version = '1.0'
@@ -160,5 +162,25 @@ module Gitlab
     config.generators do |g|
       g.factory_girl false
     end
+
+    config.after_initialize do
+      Rails.application.reload_routes!
+
+      project_url_helpers = Module.new do
+        Gitlab::Application.routes.named_routes.helper_names.each do |name|
+          next unless name.include?('namespace_project')
+
+          define_method(name.sub('namespace_project', 'project')) do |project, *args|
+            send(name, project&.namespace, project, *args)
+          end
+        end
+      end
+
+      Gitlab::Routing.url_helpers.include project_url_helpers
+      Gitlab::Routing.url_helpers.extend project_url_helpers
+
+      GitlabRoutingHelper.include project_url_helpers
+      GitlabRoutingHelper.extend project_url_helpers
+    end
   end
 end
diff --git a/config/boot.rb b/config/boot.rb
index db5ab9180216b208d8c9591810a369177c4d9bf7..02baeab29ab69d8270c72314046f94709f6454e3 100644
--- a/config/boot.rb
+++ b/config/boot.rb
@@ -5,17 +5,13 @@ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
 
 require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
 
-# set default directory for multiproces metrics gathering
-ENV['prometheus_multiproc_dir'] ||= 'tmp/prometheus_multiproc_dir'
+begin
+  require 'bootsnap/setup'
+rescue SystemCallError => exception
+  $stderr.puts "WARNING: Bootsnap failed to setup: #{exception.message}"
+end
 
-# Default Bootsnap configuration from https://github.com/Shopify/bootsnap#usage
-require 'bootsnap'
-Bootsnap.setup(
-  cache_dir:            'tmp/cache',
-  development_mode:     ENV['RAILS_ENV'] == 'development',
-  load_path_cache:      true,
-  autoload_paths_cache: true,
-  disable_trace:        false,
-  compile_cache_iseq:   true,
-  compile_cache_yaml:   true
-)
+# set default directory for multiproces metrics gathering
+if ENV['RAILS_ENV'] == 'development' || ENV['RAILS_ENV'] == 'test'
+  ENV['prometheus_multiproc_dir'] ||= 'tmp/prometheus_multiproc_dir'
+end
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 0b33783869b7b514e3ae738853b3b3468de17e05..4b81fd90f593eaa1de82a42cc9499754c3034103 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -454,6 +454,10 @@ production: &base
     # introduced in 9.0). Eventually Gitaly use will become mandatory and
     # this option will disappear.
     enabled: true
+    # Default Gitaly authentication token. Can be overriden per storage. Can
+    # be left blank when Gitaly is running locally on a Unix socket, which
+    # is the normal way to deploy Gitaly.
+    token:
 
   #
   # 4. Advanced settings
@@ -469,6 +473,7 @@ production: &base
       default:
         path: /home/git/repositories/
         gitaly_address: unix:/home/git/gitlab/tmp/sockets/private/gitaly.socket # TCP connections are supported too (e.g. tcp://host:port)
+        # gitaly_token: 'special token' # Optional: override global gitaly.token for this storage.
 
   ## Backup settings
   backup:
@@ -538,6 +543,10 @@ production: &base
     #   enabled: true
     #   host: localhost
     #   port: 3808
+  prometheus:
+    # Time between sampling of unicorn socket metrics, in seconds
+    # unicorn_sampler_interval: 10
+
 
   #
   # 5. Extra customization
@@ -594,6 +603,7 @@ test:
         gitaly_address: unix:tmp/tests/gitaly/gitaly.socket
   gitaly:
     enabled: true
+    token: secret
   backup:
     path: tmp/tests/backups
   gitlab_shell:
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 8ddf8e4d2e42745579c55c68baaca52a52e20e94..cb11d2c34f4877cf9b2b76c51806f5cd0374d68f 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -494,6 +494,12 @@ Settings.webpack.dev_server['enabled'] ||= false
 Settings.webpack.dev_server['host']    ||= 'localhost'
 Settings.webpack.dev_server['port']    ||= 3808
 
+#
+# Prometheus metrics settings
+#
+Settings['prometheus'] ||= Settingslogic.new({})
+Settings.prometheus['unicorn_sampler_interval'] ||= 10
+
 #
 # Testing settings
 #
diff --git a/config/initializers/5_backend.rb b/config/initializers/5_backend.rb
index 2bd159ca7f189b4ef4452858387a91fff3bd0f09..482613dacc9c4d2b48cad98a5dac67c1337c96de 100644
--- a/config/initializers/5_backend.rb
+++ b/config/initializers/5_backend.rb
@@ -1,6 +1,8 @@
-required_version = Gitlab::VersionInfo.parse(Gitlab::Shell.version_required)
-current_version = Gitlab::VersionInfo.parse(Gitlab::Shell.new.version)
+unless Rails.env.test?
+  required_version = Gitlab::VersionInfo.parse(Gitlab::Shell.version_required)
+  current_version = Gitlab::VersionInfo.parse(Gitlab::Shell.new.version)
 
-unless current_version.valid? && required_version <= current_version
-  warn "WARNING: This version of GitLab depends on gitlab-shell #{required_version}, but you're running #{current_version}. Please update gitlab-shell."
+  unless current_version.valid? && required_version <= current_version
+    warn "WARNING: This version of GitLab depends on gitlab-shell #{required_version}, but you're running #{current_version}. Please update gitlab-shell."
+  end
 end
diff --git a/config/initializers/8_metrics.rb b/config/initializers/8_metrics.rb
index 508b886d6a0059da659499446897420708f05f6b..d56fd7a6cfa282db864a19f387ff8f9fa0e2e11c 100644
--- a/config/initializers/8_metrics.rb
+++ b/config/initializers/8_metrics.rb
@@ -119,6 +119,13 @@ def instrument_classes(instrumentation)
 end
 # rubocop:enable Metrics/AbcSize
 
+Gitlab::Metrics::UnicornSampler.initialize_instance(Settings.prometheus.unicorn_sampler_interval).start
+
+Gitlab::Application.configure do |config|
+  # 0 should be Sentry to catch errors in this middleware
+  config.middleware.insert(1, Gitlab::Metrics::ConnectionRackMiddleware)
+end
+
 if Gitlab::Metrics.enabled?
   require 'pathname'
   require 'influxdb'
@@ -154,8 +161,8 @@ if Gitlab::Metrics.enabled?
       ActiveRecord::Querying.public_instance_methods(false).map(&:to_s)
     )
 
-    Gitlab::Metrics::Instrumentation.
-      instrument_class_hierarchy(ActiveRecord::Base) do |klass, method|
+    Gitlab::Metrics::Instrumentation
+      .instrument_class_hierarchy(ActiveRecord::Base) do |klass, method|
         # Instrumenting the ApplicationSetting class can lead to an infinite
         # loop. Since the data is cached any way we don't really need to
         # instrument it.
@@ -175,7 +182,7 @@ if Gitlab::Metrics.enabled?
 
   GC::Profiler.enable
 
-  Gitlab::Metrics::Sampler.new.start
+  Gitlab::Metrics::InfluxSampler.initialize_instance.start
 
   module TrackNewRedisConnections
     def connect(*args)
diff --git a/config/initializers/active_record_data_types.rb b/config/initializers/active_record_data_types.rb
index beb97c6fce0603bdca474c8633ad9a3cebc07523..fef591c397ddb6108bc27064def9f431ce93339c 100644
--- a/config/initializers/active_record_data_types.rb
+++ b/config/initializers/active_record_data_types.rb
@@ -4,21 +4,78 @@
 if Gitlab::Database.postgresql?
   require 'active_record/connection_adapters/postgresql_adapter'
 
-  module ActiveRecord
-    module ConnectionAdapters
-      class PostgreSQLAdapter
-        NATIVE_DATABASE_TYPES.merge!(datetime_with_timezone: { name: 'timestamptz' })
+  module ActiveRecord::ConnectionAdapters::PostgreSQL::OID
+    # Add the class `DateTimeWithTimeZone` so we can map `timestamptz` to it.
+    class DateTimeWithTimeZone < DateTime
+      def type
+        :datetime_with_timezone
       end
     end
   end
+
+  module RegisterDateTimeWithTimeZone
+    # Run original `initialize_type_map` and then register `timestamptz` as a
+    # `DateTimeWithTimeZone`.
+    #
+    # Apparently it does not matter that the original `initialize_type_map`
+    # aliases `timestamptz` to `timestamp`.
+    #
+    # When schema dumping, `timestamptz` columns will be output as
+    # `t.datetime_with_timezone`.
+    def initialize_type_map(mapping)
+      super mapping
+
+      mapping.register_type 'timestamptz' do |_, _, sql_type|
+        precision = extract_precision(sql_type)
+        ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::OID::DateTimeWithTimeZone.new(precision: precision)
+      end
+    end
+  end
+
+  class ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
+    prepend RegisterDateTimeWithTimeZone
+
+    # Add column type `datetime_with_timezone` so we can do this in
+    # migrations:
+    #
+    #   add_column(:users, :datetime_with_timezone)
+    #
+    NATIVE_DATABASE_TYPES[:datetime_with_timezone] = { name: 'timestamptz' }
+  end
 elsif Gitlab::Database.mysql?
   require 'active_record/connection_adapters/mysql2_adapter'
 
-  module ActiveRecord
-    module ConnectionAdapters
-      class AbstractMysqlAdapter
-        NATIVE_DATABASE_TYPES.merge!(datetime_with_timezone: { name: 'timestamp' })
+  module RegisterDateTimeWithTimeZone
+    # Run original `initialize_type_map` and then register `timestamp` as a
+    # `MysqlDateTimeWithTimeZone`.
+    #
+    # When schema dumping, `timestamp` columns will be output as
+    # `t.datetime_with_timezone`.
+    def initialize_type_map(mapping)
+      super mapping
+
+      mapping.register_type(%r(timestamp)i) do |sql_type|
+        precision = extract_precision(sql_type)
+        ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::MysqlDateTimeWithTimeZone.new(precision: precision)
       end
     end
   end
+
+  class ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter
+    prepend RegisterDateTimeWithTimeZone
+
+    # Add the class `DateTimeWithTimeZone` so we can map `timestamp` to it.
+    class MysqlDateTimeWithTimeZone < MysqlDateTime
+      def type
+        :datetime_with_timezone
+      end
+    end
+
+    # Add column type `datetime_with_timezone` so we can do this in
+    # migrations:
+    #
+    #   add_column(:users, :datetime_with_timezone)
+    #
+    NATIVE_DATABASE_TYPES[:datetime_with_timezone] = { name: 'timestamp' }
+  end
 end
diff --git a/config/initializers/active_record_table_definition.rb b/config/initializers/active_record_table_definition.rb
index 4f59e35f4da026a0623639c0eeb17c9fc0639f44..8e3a1c7a62f785e773ad69ec49960e6eeddf8651 100644
--- a/config/initializers/active_record_table_definition.rb
+++ b/config/initializers/active_record_table_definition.rb
@@ -3,15 +3,15 @@
 
 require 'active_record/connection_adapters/abstract/schema_definitions'
 
-# Appends columns `created_at` and `updated_at` to a table.
-#
-# It is used in table creation like:
-# create_table 'users' do |t|
-#   t.timestamps_with_timezone
-# end
 module ActiveRecord
   module ConnectionAdapters
     class TableDefinition
+      # Appends columns `created_at` and `updated_at` to a table.
+      #
+      # It is used in table creation like:
+      # create_table 'users' do |t|
+      #   t.timestamps_with_timezone
+      # end
       def timestamps_with_timezone(**options)
         options[:null] = false if options[:null].nil?
 
@@ -19,6 +19,16 @@ module ActiveRecord
           column(column_name, :datetime_with_timezone, options)
         end
       end
+
+      # Adds specified column with appropriate timestamp type
+      #
+      # It is used in table creation like:
+      # create_table 'users' do |t|
+      #   t.datetime_with_timezone :did_something_at
+      # end
+      def datetime_with_timezone(column_name, **options)
+        column(column_name, :datetime_with_timezone, options)
+      end
     end
   end
 end
diff --git a/config/initializers/bootstrap_form.rb b/config/initializers/bootstrap_form.rb
new file mode 100644
index 0000000000000000000000000000000000000000..11171b38a85be6fbe61f030cfc066449dc95ff92
--- /dev/null
+++ b/config/initializers/bootstrap_form.rb
@@ -0,0 +1,7 @@
+module BootstrapFormBuilderCustomization
+  def label_class
+    "label-light"
+  end
+end
+
+BootstrapForm::FormBuilder.prepend(BootstrapFormBuilderCustomization)
diff --git a/config/initializers/doorkeeper_openid_connect.rb b/config/initializers/doorkeeper_openid_connect.rb
index 4ff9019c43c62a53196fd058728f9d215809c299..c58f425b19be7941d2d78190292ec0da933803cb 100644
--- a/config/initializers/doorkeeper_openid_connect.rb
+++ b/config/initializers/doorkeeper_openid_connect.rb
@@ -29,7 +29,7 @@ Doorkeeper::OpenidConnect.configure do
       o.claim(:email)          { |user| user.public_email  }
       o.claim(:email_verified) { |user| true if user.public_email? }
       o.claim(:website)        { |user| user.full_website_url if user.website_url? }
-      o.claim(:profile)        { |user| Rails.application.routes.url_helpers.user_url user }
+      o.claim(:profile)        { |user| Gitlab::Routing.url_helpers.user_url user }
       o.claim(:picture)        { |user| user.avatar_url(only_path: false) }
     end
   end
diff --git a/config/initializers/flipper.rb b/config/initializers/flipper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8ec9613a4b7eee61a11da8a15507dbf6279a7ec0
--- /dev/null
+++ b/config/initializers/flipper.rb
@@ -0,0 +1,6 @@
+require 'flipper/middleware/memoizer'
+
+unless Rails.env.test?
+  Rails.application.config.middleware.use Flipper::Middleware::Memoizer,
+    lambda { Feature.flipper }
+end
diff --git a/config/initializers/relative_naming_ci_namespace.rb b/config/initializers/relative_naming_ci_namespace.rb
index 03ac55be0b694d7f75aecbe97492682870ef649e..d9d3034150fb43c1357885bd830f59f9662fa8a9 100644
--- a/config/initializers/relative_naming_ci_namespace.rb
+++ b/config/initializers/relative_naming_ci_namespace.rb
@@ -4,10 +4,10 @@
 # - [project.namespace, project, build]
 #
 # instead of:
-# - namespace_project_job_path(project.namespace, project, build)
+# - project_job_path(project, build)
 #
 # Without that, Ci:: namespace is used for resolving routes:
-# - namespace_project_ci_build_path(project.namespace, project, build)
+# - project_ci_build_path(project, build)
 
 module Ci
   def self.use_relative_model_naming?
diff --git a/config/karma.config.js b/config/karma.config.js
index 978850e5d709b5adcefc069a2fdfb3f69101d2f1..2f571978e0869a767198595b001c31a2c9ed4180 100644
--- a/config/karma.config.js
+++ b/config/karma.config.js
@@ -54,6 +54,16 @@ module.exports = function(config) {
       subdir: '.',
       fixWebpackSourcePaths: true
     };
+    karmaConfig.browserNoActivityTimeout = 60000; // 60 seconds
+  }
+
+  if (process.env.DEBUG) {
+    karmaConfig.logLevel = config.LOG_DEBUG;
+    process.env.CHROME_LOG_FILE = process.env.CHROME_LOG_FILE || 'chrome_debug.log';
+  }
+
+  if (process.env.CHROME_LOG_FILE) {
+    karmaConfig.customLaunchers.ChromeHeadlessCustom.flags.push('--enable-logging', '--v=1');
   }
 
   if (process.env.DEBUG) {
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 9d47425950a1f324aed2f6824ed2f08be51f0ae2..8932db138d9890a2842f20349a0e65d6d74104ef 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -2,17 +2,63 @@
 # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
 
 en:
-  hello: "Hello world"
-  errors:
-    messages:
-      label_already_exists_at_group_level: "already exists at group level for %{group}. Please choose another one."
-      wrong_size: "is the wrong size (should be %{file_size})"
-      size_too_small: "is too small (should be at least %{file_size})"
-      size_too_big: "is too big (should be at most %{file_size})"
   views:
     pagination:
       previous: "Prev"
       next: "Next"
+  date:
+    abbr_day_names:
+    - Sun
+    - Mon
+    - Tue
+    - Wed
+    - Thu
+    - Fri
+    - Sat
+    abbr_month_names:
+    -
+    - Jan
+    - Feb
+    - Mar
+    - Apr
+    - May
+    - Jun
+    - Jul
+    - Aug
+    - Sep
+    - Oct
+    - Nov
+    - Dec
+    day_names:
+    - Sunday
+    - Monday
+    - Tuesday
+    - Wednesday
+    - Thursday
+    - Friday
+    - Saturday
+    formats:
+      default: "%Y-%m-%d"
+      long: "%B %d, %Y"
+      short: "%b %d"
+    month_names:
+    -
+    - January
+    - February
+    - March
+    - April
+    - May
+    - June
+    - July
+    - August
+    - September
+    - October
+    - November
+    - December
+    order:
+    - :year
+    - :month
+    - :day
   datetime:
     time_ago_in_words:
       half_a_minute: "half a minute ago"
@@ -49,3 +95,158 @@ en:
       almost_x_years:
         one:   "almost 1 year ago"
         other: "almost %{count} years ago"
+    distance_in_words:
+      about_x_hours:
+        one: about 1 hour
+        other: about %{count} hours
+      about_x_months:
+        one: about 1 month
+        other: about %{count} months
+      about_x_years:
+        one: about 1 year
+        other: about %{count} years
+      almost_x_years:
+        one: almost 1 year
+        other: almost %{count} years
+      half_a_minute: half a minute
+      less_than_x_minutes:
+        one: less than a minute
+        other: less than %{count} minutes
+      less_than_x_seconds:
+        one: less than 1 second
+        other: less than %{count} seconds
+      over_x_years:
+        one: over 1 year
+        other: over %{count} years
+      x_days:
+        one: 1 day
+        other: "%{count} days"
+      x_minutes:
+        one: 1 minute
+        other: "%{count} minutes"
+      x_months:
+        one: 1 month
+        other: "%{count} months"
+      x_years:
+        one: 1 year
+        other: "%{count} years"
+      x_seconds:
+        one: 1 second
+        other: "%{count} seconds"
+    prompts:
+      day: Day
+      hour: Hour
+      minute: Minute
+      month: Month
+      second: Seconds
+      year: Year
+  errors:
+    format: "%{attribute} %{message}"
+    messages:
+      label_already_exists_at_group_level: "already exists at group level for %{group}. Please choose another one."
+      wrong_size: "is the wrong size (should be %{file_size})"
+      size_too_small: "is too small (should be at least %{file_size})"
+      size_too_big: "is too big (should be at most %{file_size})"
+      accepted: must be accepted
+      blank: can't be blank
+      present: must be blank
+      confirmation: doesn't match %{attribute}
+      empty: can't be empty
+      equal_to: must be equal to %{count}
+      even: must be even
+      exclusion: is reserved
+      greater_than: must be greater than %{count}
+      greater_than_or_equal_to: must be greater than or equal to %{count}
+      inclusion: is not included in the list
+      invalid: is invalid
+      less_than: must be less than %{count}
+      less_than_or_equal_to: must be less than or equal to %{count}
+      model_invalid: "Validation failed: %{errors}"
+      not_a_number: is not a number
+      not_an_integer: must be an integer
+      odd: must be odd
+      required: must exist
+      taken: has already been taken
+      too_long:
+        one: is too long (maximum is 1 character)
+        other: is too long (maximum is %{count} characters)
+      too_short:
+        one: is too short (minimum is 1 character)
+        other: is too short (minimum is %{count} characters)
+      wrong_length:
+        one: is the wrong length (should be 1 character)
+        other: is the wrong length (should be %{count} characters)
+      other_than: must be other than %{count}
+    template:
+      body: 'There were problems with the following fields:'
+      header:
+        one: 1 error prohibited this %{model} from being saved
+        other: "%{count} errors prohibited this %{model} from being saved"
+  helpers:
+    select:
+      prompt: Please select
+    submit:
+      create: Create %{model}
+      submit: Save %{model}
+      update: Update %{model}
+  number:
+    currency:
+      format:
+        delimiter: ","
+        format: "%u%n"
+        precision: 2
+        separator: "."
+        significant: false
+        strip_insignificant_zeros: false
+        unit: "$"
+    format:
+      delimiter: ","
+      precision: 3
+      separator: "."
+      significant: false
+      strip_insignificant_zeros: false
+    human:
+      decimal_units:
+        format: "%n %u"
+        units:
+          billion: Billion
+          million: Million
+          quadrillion: Quadrillion
+          thousand: Thousand
+          trillion: Trillion
+          unit: ''
+      format:
+        delimiter: ''
+        precision: 3
+        significant: true
+        strip_insignificant_zeros: true
+      storage_units:
+        format: "%n %u"
+        units:
+          byte:
+            one: Byte
+            other: Bytes
+          gb: GB
+          kb: KB
+          mb: MB
+          tb: TB
+    percentage:
+      format:
+        delimiter: ''
+        format: "%n%"
+    precision:
+      format:
+        delimiter: ''
+  support:
+    array:
+      last_word_connector: ", and "
+      two_words_connector: " and "
+      words_connector: ", "
+  time:
+    am: am
+    formats:
+      default: "%a, %d %b %Y %H:%M:%S %z"
+      long: "%B %d, %Y %H:%M"
+      short: "%d %b %H:%M"
+      timeago_tooltip: "%b %-d, %Y %-l:%M%P"
+    pm: pm
diff --git a/config/locales/es.yml b/config/locales/es.yml
index d71c6eb5047524f93bf118a116a11a5fa312f2b7..fdc52b4ae11e19743b0e64da6c5e0f8b902dedc8 100644
--- a/config/locales/es.yml
+++ b/config/locales/es.yml
@@ -251,4 +251,5 @@ es:
       default: "%A, %d de %B de %Y %H:%M:%S %z"
       long: "%d de %B de %Y %H:%M"
       short: "%d de %b %H:%M"
+      timeago_tooltip: "%d de %B de %Y %H:%M"
     pm: pm
diff --git a/config/prometheus/additional_metrics.yml b/config/prometheus/additional_metrics.yml
new file mode 100644
index 0000000000000000000000000000000000000000..daecde49570646958ca3f3a7df283690abbfc87a
--- /dev/null
+++ b/config/prometheus/additional_metrics.yml
@@ -0,0 +1,32 @@
+- group: Kubernetes
+  priority: 1
+  metrics:
+  - title: "Memory usage"
+    y_label: "Values"
+    required_metrics:
+      - container_memory_usage_bytes
+    weight: 1
+    queries:
+    - query_range: 'avg(container_memory_usage_bytes{%{environment_filter}}) / 2^20'
+      label: Container memory
+      unit: MiB
+  - title: "Current memory usage"
+    required_metrics:
+     - container_memory_usage_bytes
+    weight: 1
+    queries:
+    - query: 'avg(container_memory_usage_bytes{%{environment_filter}}) / 2^20'
+      display_empty: false
+      unit: MiB
+  - title: "CPU usage"
+    required_metrics:
+     - container_cpu_usage_seconds_total
+    weight: 1
+    queries:
+    - query_range: 'avg(rate(container_cpu_usage_seconds_total{%{environment_filter}}[2m])) * 100'
+  - title: "Current CPU usage"
+    required_metrics:
+     - container_cpu_usage_seconds_total
+    weight: 1
+    queries:
+    - query: 'avg(rate(container_cpu_usage_seconds_total{%{environment_filter}}[2m])) * 100'
diff --git a/config/routes/project.rb b/config/routes/project.rb
index f95cc3101d38d8bb778e6ae86256a8296da29756..0d0a8dff25e3c82003583d46e588f1ce4c4f3d48 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -73,6 +73,10 @@ constraints(ProjectUrlConstrainer.new) do
 
       resource :mattermost, only: [:new, :create]
 
+      namespace :prometheus do
+        get :active_metrics
+      end
+
       resources :deploy_keys, constraints: { id: /\d+/ }, only: [:index, :new, :create, :edit, :update] do
         member do
           put :enable
@@ -83,13 +87,8 @@ constraints(ProjectUrlConstrainer.new) do
       resources :forks, only: [:index, :new, :create]
       resource :import, only: [:new, :create, :show]
 
-      resources :merge_requests, concerns: :awardable, constraints: { id: /\d+/ } do
+      resources :merge_requests, concerns: :awardable, except: [:new, :create], constraints: { id: /\d+/ } do
         member do
-          get :commits
-          get :diffs
-          get :conflicts
-          get :conflict_for_path
-          get :pipelines
           get :commit_change_content
           post :merge
           post :cancel_merge_when_pipeline_succeeds
@@ -97,18 +96,32 @@ constraints(ProjectUrlConstrainer.new) do
           get :ci_environments_status
           post :toggle_subscription
           post :remove_wip
-          get :diff_for_path
-          post :resolve_conflicts
           post :assign_related_issues
+
+          scope constraints: { format: nil }, action: :show do
+            get :commits, defaults: { tab: 'commits' }
+            get :pipelines, defaults: { tab: 'pipelines' }
+            get :diffs, defaults: { tab: 'diffs' }
+          end
+
+          scope constraints: { format: 'json' }, as: :json do
+            get :commits
+            get :pipelines
+            get :diffs, to: 'merge_requests/diffs#show'
+          end
+
+          get :diff_for_path, controller: 'merge_requests/diffs'
+
+          scope controller: 'merge_requests/conflicts' do
+            get :conflicts, action: :show
+            get :conflict_for_path
+            post :resolve_conflicts
+          end
         end
 
         collection do
-          get :branch_from
-          get :branch_to
-          get :update_branches
           get :diff_for_path
           post :bulk_update
-          get :new_diffs, path: 'new/diffs'
         end
 
         resources :discussions, only: [], constraints: { id: /\h{40}/ } do
@@ -119,6 +132,29 @@ constraints(ProjectUrlConstrainer.new) do
         end
       end
 
+      controller 'merge_requests/creations', path: 'merge_requests' do
+        post '', action: :create, as: nil
+
+        scope path: 'new', as: :new_merge_request do
+          get '', action: :new
+
+          scope constraints: { format: nil }, action: :new do
+            get :diffs, defaults: { tab: 'diffs' }
+            get :pipelines, defaults: { tab: 'pipelines' }
+          end
+
+          scope constraints: { format: 'json' }, as: :json do
+            get :diffs
+            get :pipelines
+          end
+
+          get :diff_for_path
+          get :update_branches
+          get :branch_from
+          get :branch_to
+        end
+      end
+
       resources :variables, only: [:index, :show, :update, :create, :destroy]
       resources :triggers, only: [:index, :create, :edit, :update, :destroy] do
         member do
@@ -153,6 +189,7 @@ constraints(ProjectUrlConstrainer.new) do
           post :stop
           get :terminal
           get :metrics
+          get :additional_metrics
           get '/terminal.ws/authorize', to: 'environments#terminal_websocket_authorize', constraints: { format: nil }
         end
 
@@ -163,6 +200,7 @@ constraints(ProjectUrlConstrainer.new) do
         resources :deployments, only: [:index] do
           member do
             get :metrics
+            get :additional_metrics
           end
         end
       end
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 3c2455ebf35955aee9e10ab248d8a15d4e759667..cbb0a899638aba0a6ce3fff818ce767b5807f15e 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -11,22 +11,13 @@ var WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeMod
 
 var ROOT_PATH = path.resolve(__dirname, '..');
 var IS_PRODUCTION = process.env.NODE_ENV === 'production';
-var IS_DEV_SERVER = process.argv[1].indexOf('webpack-dev-server') !== -1;
+var IS_DEV_SERVER = process.argv.join(' ').indexOf('webpack-dev-server') !== -1;
 var DEV_SERVER_HOST = process.env.DEV_SERVER_HOST || 'localhost';
 var DEV_SERVER_PORT = parseInt(process.env.DEV_SERVER_PORT, 10) || 3808;
 var DEV_SERVER_LIVERELOAD = process.env.DEV_SERVER_LIVERELOAD !== 'false';
 var WEBPACK_REPORT = process.env.WEBPACK_REPORT;
 var NO_COMPRESSION = process.env.NO_COMPRESSION;
 
-// optional dependency `node-zopfli` is unavailable on CentOS 6
-var ZOPFLI_AVAILABLE;
-try {
-  require.resolve('node-zopfli');
-  ZOPFLI_AVAILABLE = true;
-} catch(err) {
-  ZOPFLI_AVAILABLE = false;
-}
-
 var config = {
   // because sqljs requires fs.
   node: {
@@ -64,6 +55,7 @@ var config = {
     pipelines:            './pipelines/pipelines_bundle.js',
     pipelines_details:     './pipelines/pipeline_details_bundle.js',
     profile:              './profile/profile_bundle.js',
+    prometheus_metrics:   './prometheus_metrics',
     protected_branches:   './protected_branches/protected_branches_bundle.js',
     protected_tags:       './protected_tags',
     sidebar:              './sidebar/sidebar_bundle.js',
@@ -79,6 +71,7 @@ var config = {
     vue_merge_request_widget: './vue_merge_request_widget/index.js',
     test:                 './test.js',
     peek:                 './peek.js',
+    webpack_runtime:      './webpack.js',
   },
 
   output: {
@@ -171,6 +164,7 @@ var config = {
         'issue_show',
         'job_details',
         'merge_conflicts',
+        'monitoring',
         'notebook_viewer',
         'pdf_viewer',
         'pipelines',
@@ -197,7 +191,7 @@ var config = {
 
     // create cacheable common library bundles
     new webpack.optimize.CommonsChunkPlugin({
-      names: ['main', 'locale', 'common', 'runtime'],
+      names: ['main', 'locale', 'common', 'webpack_runtime'],
     }),
   ],
 
@@ -233,12 +227,12 @@ if (IS_PRODUCTION) {
 
   // zopfli requires a lot of compute time and is disabled in CI
   if (!NO_COMPRESSION) {
-    config.plugins.push(
-      new CompressionPlugin({
-        asset: '[path].gz[query]',
-        algorithm: ZOPFLI_AVAILABLE ? 'zopfli' : 'gzip',
-      })
-    );
+    // gracefully fall back to gzip if `node-zopfli` is unavailable (e.g. in CentOS 6)
+    try {
+      config.plugins.push(new CompressionPlugin({ algorithm: 'zopfli' }));
+    } catch(err) {
+      config.plugins.push(new CompressionPlugin({ algorithm: 'gzip' }));
+    }
   }
 }
 
@@ -249,13 +243,16 @@ if (IS_DEV_SERVER) {
     port: DEV_SERVER_PORT,
     headers: { 'Access-Control-Allow-Origin': '*' },
     stats: 'errors-only',
+    hot: DEV_SERVER_LIVERELOAD,
     inline: DEV_SERVER_LIVERELOAD
   };
-  config.output.publicPath = '//' + DEV_SERVER_HOST + ':' + DEV_SERVER_PORT + config.output.publicPath;
   config.plugins.push(
     // watch node_modules for changes if we encounter a missing module compile error
     new WatchMissingNodeModulesPlugin(path.join(ROOT_PATH, 'node_modules'))
   );
+  if (DEV_SERVER_LIVERELOAD) {
+    config.plugins.push(new webpack.HotModuleReplacementPlugin());
+  }
 }
 
 if (WEBPACK_REPORT) {
diff --git a/db/fixtures/development/19_environments.rb b/db/fixtures/development/19_environments.rb
index 93214b9d3e7e1fb0fed57a0ed66ebef74bb5fa8b..c1bbc9af6d6f84d4e3cb3e408bf317edab054336 100644
--- a/db/fixtures/development/19_environments.rb
+++ b/db/fixtures/development/19_environments.rb
@@ -33,7 +33,7 @@ class Gitlab::Seeder::Environments
 
       create_deployment!(
         merge_request.source_project,
-        "review/#{merge_request.source_branch}",
+        "review/#{merge_request.source_branch.gsub(/[^a-zA-Z0-9]/, '')}",
         merge_request.source_branch,
         merge_request.diff_head_sha
       )
diff --git a/db/migrate/20160615191922_set_missing_stage_on_ci_builds.rb b/db/migrate/20160615191922_set_missing_stage_on_ci_builds.rb
index 4d6a61bd6142404ae8912a20aabaa02327c94160..5336b036bcad2d19a7b67a0fbcbe9590304ccf67 100644
--- a/db/migrate/20160615191922_set_missing_stage_on_ci_builds.rb
+++ b/db/migrate/20160615191922_set_missing_stage_on_ci_builds.rb
@@ -2,6 +2,8 @@
 class SetMissingStageOnCiBuilds < ActiveRecord::Migration
   include Gitlab::Database::MigrationHelpers
 
+  disable_ddl_transaction!
+
   def up
     update_column_in_batches(:ci_builds, :stage, :test) do |table, query|
       query.where(table[:stage].eq(nil))
diff --git a/db/migrate/20160721081015_drop_and_readd_has_external_wiki_in_projects.rb b/db/migrate/20160721081015_drop_and_readd_has_external_wiki_in_projects.rb
index b2a2ce41391092ccb5e6b036d8bf68a1e90c6334..abe8e701e23d76e0f5d9c150675c9465903115c4 100644
--- a/db/migrate/20160721081015_drop_and_readd_has_external_wiki_in_projects.rb
+++ b/db/migrate/20160721081015_drop_and_readd_has_external_wiki_in_projects.rb
@@ -5,6 +5,8 @@ class DropAndReaddHasExternalWikiInProjects < ActiveRecord::Migration
   # Set this constant to true if this migration requires downtime.
   DOWNTIME = false
 
+  disable_ddl_transaction!
+
   def up
     update_column_in_batches(:projects, :has_external_wiki, nil) do |table, query|
       query.where(table[:has_external_wiki].not_eq(nil))
diff --git a/db/migrate/20160804142904_add_ci_config_file_to_project.rb b/db/migrate/20160804142904_add_ci_config_file_to_project.rb
new file mode 100644
index 0000000000000000000000000000000000000000..341ae555c1b95964fd7841f0b597b29165293590
--- /dev/null
+++ b/db/migrate/20160804142904_add_ci_config_file_to_project.rb
@@ -0,0 +1,11 @@
+class AddCiConfigFileToProject < ActiveRecord::Migration
+  DOWNTIME = false
+
+  def change
+    add_column :projects, :ci_config_path, :string
+  end
+
+  def down
+    remove_column :projects, :ci_config_path
+  end
+end
diff --git a/db/migrate/20160901141443_set_confidential_issues_events_on_webhooks.rb b/db/migrate/20160901141443_set_confidential_issues_events_on_webhooks.rb
index febd2c0e65ebce90e7967f8abea75bb355c0ffec..f8486e3e1a6140929a8abc74ed2a3da29acd3112 100644
--- a/db/migrate/20160901141443_set_confidential_issues_events_on_webhooks.rb
+++ b/db/migrate/20160901141443_set_confidential_issues_events_on_webhooks.rb
@@ -4,6 +4,8 @@ class SetConfidentialIssuesEventsOnWebhooks < ActiveRecord::Migration
 
   DOWNTIME = false
 
+  disable_ddl_transaction!
+
   def up
     update_column_in_batches(:web_hooks, :confidential_issues_events, true) do |table, query|
       query.where(table[:issues_events].eq(true))
diff --git a/db/migrate/20160919144305_add_type_to_labels.rb b/db/migrate/20160919144305_add_type_to_labels.rb
index 2d2725ccf5900d7af2fb4a2e431e2290660ac72b..d08b339cd279153b46af35cd291e134991b8fa48 100644
--- a/db/migrate/20160919144305_add_type_to_labels.rb
+++ b/db/migrate/20160919144305_add_type_to_labels.rb
@@ -5,6 +5,8 @@ class AddTypeToLabels < ActiveRecord::Migration
   DOWNTIME = true
   DOWNTIME_REASON = 'Labels will not work as expected until this migration is complete.'
 
+  disable_ddl_transaction!
+
   def change
     add_column :labels, :type, :string
 
diff --git a/db/migrate/20161018124658_make_project_owners_masters.rb b/db/migrate/20161018124658_make_project_owners_masters.rb
index fe11699c196ff0d4010a5d7387f1737a9f672069..cb93b449067ae4b7587697fcbd0ff1f34f3e9cbd 100644
--- a/db/migrate/20161018124658_make_project_owners_masters.rb
+++ b/db/migrate/20161018124658_make_project_owners_masters.rb
@@ -4,6 +4,8 @@ class MakeProjectOwnersMasters < ActiveRecord::Migration
 
   DOWNTIME = false
 
+  disable_ddl_transaction!
+
   def up
     update_column_in_batches(:members, :access_level, 40) do |table, query|
       query.where(table[:access_level].eq(50).and(table[:source_type].eq('Project')))
diff --git a/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb b/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb
index e5292cfba079cba9f6279b1879dc625da867860d..c0cb9d7874890262df43f8d40ed88288d20d59bb 100644
--- a/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb
+++ b/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb
@@ -6,9 +6,9 @@ class MigrateProcessCommitWorkerJobs < ActiveRecord::Migration
 
   class Project < ActiveRecord::Base
     def self.find_including_path(id)
-      select("projects.*, CONCAT(namespaces.path, '/', projects.path) AS path_with_namespace").
-        joins('INNER JOIN namespaces ON namespaces.id = projects.namespace_id').
-        find_by(id: id)
+      select("projects.*, CONCAT(namespaces.path, '/', projects.path) AS path_with_namespace")
+        .joins('INNER JOIN namespaces ON namespaces.id = projects.namespace_id')
+        .find_by(id: id)
     end
 
     def repository_storage_path
diff --git a/db/migrate/20161207231620_fixup_environment_name_uniqueness.rb b/db/migrate/20161207231620_fixup_environment_name_uniqueness.rb
index a20a903a7522cd83a1beba7c2e415308cd3141d1..f73e4f6c99b19762a024fa410f77fabe0432d5e9 100644
--- a/db/migrate/20161207231620_fixup_environment_name_uniqueness.rb
+++ b/db/migrate/20161207231620_fixup_environment_name_uniqueness.rb
@@ -8,11 +8,11 @@ class FixupEnvironmentNameUniqueness < ActiveRecord::Migration
     environments = Arel::Table.new(:environments)
 
     # Get all [project_id, name] pairs that occur more than once
-    finder_sql = environments.
-      group(environments[:project_id], environments[:name]).
-      having(Arel.sql("COUNT(1)").gt(1)).
-      project(environments[:project_id], environments[:name]).
-      to_sql
+    finder_sql = environments
+      .group(environments[:project_id], environments[:name])
+      .having(Arel.sql("COUNT(1)").gt(1))
+      .project(environments[:project_id], environments[:name])
+      .to_sql
 
     conflicting = connection.exec_query(finder_sql)
 
@@ -28,12 +28,12 @@ class FixupEnvironmentNameUniqueness < ActiveRecord::Migration
   # Rename conflicting environments by appending "-#{id}" to all but the first
   def fix_duplicates(project_id, name)
     environments = Arel::Table.new(:environments)
-    finder_sql = environments.
-      where(environments[:project_id].eq(project_id)).
-      where(environments[:name].eq(name)).
-      order(environments[:id].asc).
-      project(environments[:id], environments[:name]).
-      to_sql
+    finder_sql = environments
+      .where(environments[:project_id].eq(project_id))
+      .where(environments[:name].eq(name))
+      .order(environments[:id].asc)
+      .project(environments[:id], environments[:name])
+      .to_sql
 
     # Now we have the data for all the conflicting rows
     conflicts = connection.exec_query(finder_sql).rows
@@ -41,11 +41,11 @@ class FixupEnvironmentNameUniqueness < ActiveRecord::Migration
 
     conflicts.each do |id, name|
       update_sql =
-        Arel::UpdateManager.new(ActiveRecord::Base).
-          table(environments).
-          set(environments[:name] => name + "-" + id.to_s).
-          where(environments[:id].eq(id)).
-          to_sql
+        Arel::UpdateManager.new(ActiveRecord::Base)
+          .table(environments)
+          .set(environments[:name] => name + "-" + id.to_s)
+          .where(environments[:id].eq(id))
+          .to_sql
 
       connection.exec_update(update_sql, self.class.name, [])
     end
diff --git a/db/migrate/20161207231626_add_environment_slug.rb b/db/migrate/20161207231626_add_environment_slug.rb
index 8e98ee5b9ba18fd3488fe2bf4e1d256637e971b2..83cdd484c4cd0b97e19af2bdfadb22587cc96133 100644
--- a/db/migrate/20161207231626_add_environment_slug.rb
+++ b/db/migrate/20161207231626_add_environment_slug.rb
@@ -19,10 +19,10 @@ class AddEnvironmentSlug < ActiveRecord::Migration
     finder = environments.project(:id, :name)
 
     connection.exec_query(finder.to_sql).rows.each do |id, name|
-      updater = Arel::UpdateManager.new(ActiveRecord::Base).
-        table(environments).
-        set(environments[:slug] => generate_slug(name)).
-        where(environments[:id].eq(id))
+      updater = Arel::UpdateManager.new(ActiveRecord::Base)
+        .table(environments)
+        .set(environments[:slug] => generate_slug(name))
+        .where(environments[:id].eq(id))
 
       connection.exec_update(updater.to_sql, self.class.name, [])
     end
diff --git a/db/migrate/20161227192806_rename_slack_and_mattermost_notification_services.rb b/db/migrate/20161227192806_rename_slack_and_mattermost_notification_services.rb
index c7cada6dfc523f9f997e11e1b89d297d3a2cff82..6b15e5caccf404c5c068c3ce44493630b286db90 100644
--- a/db/migrate/20161227192806_rename_slack_and_mattermost_notification_services.rb
+++ b/db/migrate/20161227192806_rename_slack_and_mattermost_notification_services.rb
@@ -4,6 +4,8 @@ class RenameSlackAndMattermostNotificationServices < ActiveRecord::Migration
 
   DOWNTIME = false
 
+  disable_ddl_transaction!
+
   def up
     update_column_in_batches(:services, :type, 'SlackService') do |table, query|
       query.where(table[:type].eq('SlackNotificationService'))
diff --git a/db/migrate/20170316163800_rename_system_namespaces.rb b/db/migrate/20170316163800_rename_system_namespaces.rb
index b5408fbf1122ab8109ec33fb7e81148bca5569f2..9e9fb5ac2256dd64697d7580ebd05092ad695898 100644
--- a/db/migrate/20170316163800_rename_system_namespaces.rb
+++ b/db/migrate/20170316163800_rename_system_namespaces.rb
@@ -159,9 +159,9 @@ class RenameSystemNamespaces < ActiveRecord::Migration
   end
 
   def system_namespace
-    @system_namespace ||= Namespace.where(parent_id: nil).
-                            where(arel_table[:path].matches(system_namespace_path)).
-                            first
+    @system_namespace ||= Namespace.where(parent_id: nil)
+                            .where(arel_table[:path].matches(system_namespace_path))
+                            .first
   end
 
   def system_namespace_path
@@ -209,8 +209,8 @@ class RenameSystemNamespaces < ActiveRecord::Migration
   end
 
   def repo_paths_for_namespace(namespace)
-    projects_for_namespace(namespace).distinct.
-      select(:repository_storage).map(&:repository_storage_path)
+    projects_for_namespace(namespace).distinct
+      .select(:repository_storage).map(&:repository_storage_path)
   end
 
   def uploads_dir
diff --git a/db/migrate/20170503140202_turn_nested_groups_into_regular_groups_for_mysql.rb b/db/migrate/20170503140202_turn_nested_groups_into_regular_groups_for_mysql.rb
index c67690642c98b2a450c5e6405f4f3c7e32ab84a3..33908ae115630efbeec47461c00969898edced29 100644
--- a/db/migrate/20170503140202_turn_nested_groups_into_regular_groups_for_mysql.rb
+++ b/db/migrate/20170503140202_turn_nested_groups_into_regular_groups_for_mysql.rb
@@ -87,8 +87,8 @@ class TurnNestedGroupsIntoRegularGroupsForMysql < ActiveRecord::Migration
     while current&.parent_id
       # We're using find_by(id: ...) here to deal with cases where the
       # parent_id may point to a missing row.
-      current = Namespace.unscoped.select([:id, :parent_id]).
-        find_by(id: current.parent_id)
+      current = Namespace.unscoped.select([:id, :parent_id])
+        .find_by(id: current.parent_id)
 
       ancestors << current.id if current
     end
@@ -99,11 +99,11 @@ class TurnNestedGroupsIntoRegularGroupsForMysql < ActiveRecord::Migration
   # Returns a relation containing all the members that have access to any of
   # the current namespace's parent namespaces.
   def all_members_for(namespace)
-    Member.
-      unscoped.
-      select(['user_id', 'MAX(access_level) AS access_level']).
-      where(source_type: 'Namespace', source_id: ancestors_for(namespace)).
-      group(:user_id)
+    Member
+      .unscoped
+      .select(['user_id', 'MAX(access_level) AS access_level'])
+      .where(source_type: 'Namespace', source_id: ancestors_for(namespace))
+      .group(:user_id)
   end
 
   def bulk_insert_members(rows)
diff --git a/db/migrate/20170526185602_add_stage_id_to_ci_builds.rb b/db/migrate/20170526185602_add_stage_id_to_ci_builds.rb
index d5675d5828bfe0c3ef89d5e64d125f2b6be53a9a..d27cba76d8199e81ed03e52668dceb91912816b5 100644
--- a/db/migrate/20170526185602_add_stage_id_to_ci_builds.rb
+++ b/db/migrate/20170526185602_add_stage_id_to_ci_builds.rb
@@ -3,19 +3,11 @@ class AddStageIdToCiBuilds < ActiveRecord::Migration
 
   DOWNTIME = false
 
-  disable_ddl_transaction!
-
   def up
     add_column :ci_builds, :stage_id, :integer
-
-    add_concurrent_foreign_key :ci_builds, :ci_stages, column: :stage_id, on_delete: :cascade
-    add_concurrent_index :ci_builds, :stage_id
   end
 
   def down
-    remove_foreign_key :ci_builds, column: :stage_id
-    remove_concurrent_index :ci_builds, :stage_id
-
     remove_column :ci_builds, :stage_id, :integer
   end
 end
diff --git a/db/migrate/20170608171156_create_merge_request_diff_files.rb b/db/migrate/20170608171156_create_merge_request_diff_files.rb
new file mode 100644
index 0000000000000000000000000000000000000000..bf0c0d29adcb4add28b4201b71a322aac281d478
--- /dev/null
+++ b/db/migrate/20170608171156_create_merge_request_diff_files.rb
@@ -0,0 +1,22 @@
+class CreateMergeRequestDiffFiles < ActiveRecord::Migration
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def change
+    create_table :merge_request_diff_files, id: false do |t|
+      t.belongs_to :merge_request_diff, null: false, foreign_key: { on_delete: :cascade }
+      t.integer :relative_order, null: false
+      t.boolean :new_file, null: false
+      t.boolean :renamed_file, null: false
+      t.boolean :deleted_file, null: false
+      t.boolean :too_large, null: false
+      t.string :a_mode, null: false
+      t.string :b_mode, null: false
+      t.text :new_path, null: false
+      t.text :old_path, null: false
+      t.text :diff, null: false
+      t.index [:merge_request_diff_id, :relative_order], name: 'index_merge_request_diff_files_on_mr_diff_id_and_order', unique: true
+    end
+  end
+end
diff --git a/db/migrate/20170614115405_merge_request_diff_file_limits_to_mysql.rb b/db/migrate/20170614115405_merge_request_diff_file_limits_to_mysql.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4c1cf08aa067282ca21d72a1db61e3ea597053d6
--- /dev/null
+++ b/db/migrate/20170614115405_merge_request_diff_file_limits_to_mysql.rb
@@ -0,0 +1 @@
+require_relative 'merge_request_diff_file_limits_to_mysql'
diff --git a/db/migrate/20170619144837_add_index_for_head_pipeline_merge_request.rb b/db/migrate/20170619144837_add_index_for_head_pipeline_merge_request.rb
new file mode 100644
index 0000000000000000000000000000000000000000..02863bee0825533cda5d68e654d29dae5a7978b7
--- /dev/null
+++ b/db/migrate/20170619144837_add_index_for_head_pipeline_merge_request.rb
@@ -0,0 +1,15 @@
+class AddIndexForHeadPipelineMergeRequest < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def up
+    add_concurrent_index :merge_requests, :head_pipeline_id
+  end
+
+  def down
+    remove_concurrent_index :merge_requests, :head_pipeline_id if index_exists?(:merge_requests, :head_pipeline_id)
+  end
+end
diff --git a/db/migrate/20170622135451_rename_duplicated_variable_key.rb b/db/migrate/20170622135451_rename_duplicated_variable_key.rb
new file mode 100644
index 0000000000000000000000000000000000000000..368718ab0ce787d95f66894ea053e04006745156
--- /dev/null
+++ b/db/migrate/20170622135451_rename_duplicated_variable_key.rb
@@ -0,0 +1,38 @@
+class RenameDuplicatedVariableKey < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def up
+    execute(<<~SQL)
+      UPDATE ci_variables
+      SET #{key} = CONCAT(#{key}, #{underscore}, id)
+      WHERE id IN (
+        SELECT *
+        FROM ( -- MySQL requires an extra layer
+          SELECT dup.id
+          FROM ci_variables dup
+          INNER JOIN (SELECT max(id) AS id, #{key}, project_id
+                      FROM ci_variables tmp
+                      GROUP BY #{key}, project_id) var
+          USING (#{key}, project_id) where dup.id <> var.id
+        ) dummy
+      )
+    SQL
+  end
+
+  def down
+    # noop
+  end
+
+  def key
+    # key needs to be quoted in MySQL
+    quote_column_name('key')
+  end
+
+  def underscore
+    quote('_')
+  end
+end
diff --git a/db/migrate/20170622135628_add_environment_scope_to_ci_variables.rb b/db/migrate/20170622135628_add_environment_scope_to_ci_variables.rb
new file mode 100644
index 0000000000000000000000000000000000000000..17fe062d8d59272a3ebeb6857541183d65bb1029
--- /dev/null
+++ b/db/migrate/20170622135628_add_environment_scope_to_ci_variables.rb
@@ -0,0 +1,15 @@
+class AddEnvironmentScopeToCiVariables < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def up
+    add_column_with_default(:ci_variables, :environment_scope, :string, default: '*')
+  end
+
+  def down
+    remove_column(:ci_variables, :environment_scope)
+  end
+end
diff --git a/db/migrate/20170622135728_add_unique_constraint_to_ci_variables.rb b/db/migrate/20170622135728_add_unique_constraint_to_ci_variables.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8b2cc40ee5929655e7020358d956ce2c62459d1b
--- /dev/null
+++ b/db/migrate/20170622135728_add_unique_constraint_to_ci_variables.rb
@@ -0,0 +1,38 @@
+class AddUniqueConstraintToCiVariables < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def up
+    unless this_index_exists?
+      add_concurrent_index(:ci_variables, columns, name: index_name, unique: true)
+    end
+  end
+
+  def down
+    if this_index_exists?
+      if Gitlab::Database.mysql? && !index_exists?(:ci_variables, :project_id)
+        # Need to add this index for MySQL project_id foreign key constraint
+        add_concurrent_index(:ci_variables, :project_id)
+      end
+
+      remove_concurrent_index(:ci_variables, columns, name: index_name)
+    end
+  end
+
+  private
+
+  def this_index_exists?
+    index_exists?(:ci_variables, columns, name: index_name)
+  end
+
+  def columns
+    @columns ||= [:project_id, :key, :environment_scope]
+  end
+
+  def index_name
+    'index_ci_variables_on_project_id_and_key_and_environment_scope'
+  end
+end
diff --git a/db/migrate/20170622162730_add_ref_fetched_to_merge_request.rb b/db/migrate/20170622162730_add_ref_fetched_to_merge_request.rb
new file mode 100644
index 0000000000000000000000000000000000000000..62aa1a4b4f05fb16698fc8081fa7bb9ec6145d58
--- /dev/null
+++ b/db/migrate/20170622162730_add_ref_fetched_to_merge_request.rb
@@ -0,0 +1,9 @@
+class AddRefFetchedToMergeRequest < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  def change
+    add_column :merge_requests, :ref_fetched, :boolean
+  end
+end
diff --git a/db/migrate/20170623080805_remove_ci_variables_project_id_index.rb b/db/migrate/20170623080805_remove_ci_variables_project_id_index.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ddcc0292b9d66dad359a5ef1efe53d7e2fc7cf46
--- /dev/null
+++ b/db/migrate/20170623080805_remove_ci_variables_project_id_index.rb
@@ -0,0 +1,19 @@
+class RemoveCiVariablesProjectIdIndex < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def up
+    if index_exists?(:ci_variables, :project_id)
+      remove_concurrent_index(:ci_variables, :project_id)
+    end
+  end
+
+  def down
+    unless index_exists?(:ci_variables, :project_id)
+      add_concurrent_index(:ci_variables, :project_id)
+    end
+  end
+end
diff --git a/db/migrate/20170703102400_add_stage_id_foreign_key_to_builds.rb b/db/migrate/20170703102400_add_stage_id_foreign_key_to_builds.rb
new file mode 100644
index 0000000000000000000000000000000000000000..68b947583d3cb9ecce7ef840eff8e79bd6c604a7
--- /dev/null
+++ b/db/migrate/20170703102400_add_stage_id_foreign_key_to_builds.rb
@@ -0,0 +1,35 @@
+class AddStageIdForeignKeyToBuilds < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def up
+    unless index_exists?(:ci_builds, :stage_id)
+      add_concurrent_index(:ci_builds, :stage_id)
+    end
+
+    unless foreign_key_exists?(:ci_builds, :stage_id)
+      add_concurrent_foreign_key(:ci_builds, :ci_stages, column: :stage_id, on_delete: :cascade)
+    end
+  end
+
+  def down
+    if foreign_key_exists?(:ci_builds, :stage_id)
+      remove_foreign_key(:ci_builds, column: :stage_id)
+    end
+
+    if index_exists?(:ci_builds, :stage_id)
+      remove_concurrent_index(:ci_builds, :stage_id)
+    end
+  end
+
+  private
+
+  def foreign_key_exists?(table, column)
+    foreign_keys(:ci_builds).any? do |key|
+      key.options[:column] == column.to_s
+    end
+  end
+end
diff --git a/db/migrate/merge_request_diff_file_limits_to_mysql.rb b/db/migrate/merge_request_diff_file_limits_to_mysql.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3958380e4b901e9f62c9969a4ed78ab11500673e
--- /dev/null
+++ b/db/migrate/merge_request_diff_file_limits_to_mysql.rb
@@ -0,0 +1,12 @@
+class MergeRequestDiffFileLimitsToMysql < ActiveRecord::Migration
+  DOWNTIME = false
+
+  def up
+    return unless Gitlab::Database.mysql?
+
+    change_column :merge_request_diff_files, :diff, :text, limit: 2147483647
+  end
+
+  def down
+  end
+end
diff --git a/db/post_migrate/20161109150329_fix_project_records_with_invalid_visibility.rb b/db/post_migrate/20161109150329_fix_project_records_with_invalid_visibility.rb
index 14b5ef476f0cbbf8a5d73ed09b09081073e6341d..69007b8e8ed29e460192408f870cf17cea9b41ce 100644
--- a/db/post_migrate/20161109150329_fix_project_records_with_invalid_visibility.rb
+++ b/db/post_migrate/20161109150329_fix_project_records_with_invalid_visibility.rb
@@ -13,13 +13,13 @@ class FixProjectRecordsWithInvalidVisibility < ActiveRecord::Migration
     namespaces = Arel::Table.new(:namespaces)
 
     finder_sql =
-      projects.
-        join(namespaces, Arel::Nodes::InnerJoin).
-        on(projects[:namespace_id].eq(namespaces[:id])).
-        where(projects[:visibility_level].gt(namespaces[:visibility_level])).
-        project(projects[:id], namespaces[:visibility_level]).
-        take(BATCH_SIZE).
-        to_sql
+      projects
+        .join(namespaces, Arel::Nodes::InnerJoin)
+        .on(projects[:namespace_id].eq(namespaces[:id]))
+        .where(projects[:visibility_level].gt(namespaces[:visibility_level]))
+        .project(projects[:id], namespaces[:visibility_level])
+        .take(BATCH_SIZE)
+        .to_sql
 
     # Update matching rows in batches. Each batch can cause up to 3 UPDATE
     # statements, in addition to the SELECT: one per visibility_level
@@ -33,10 +33,10 @@ class FixProjectRecordsWithInvalidVisibility < ActiveRecord::Migration
       end
 
       updates.each do |visibility_level, project_ids|
-        updater = Arel::UpdateManager.new(ActiveRecord::Base).
-          table(projects).
-          set(projects[:visibility_level] => visibility_level).
-          where(projects[:id].in(project_ids))
+        updater = Arel::UpdateManager.new(ActiveRecord::Base)
+          .table(projects)
+          .set(projects[:visibility_level] => visibility_level)
+          .where(projects[:id].in(project_ids))
 
         ActiveRecord::Base.connection.exec_update(updater.to_sql, self.class.name, [])
       end
diff --git a/db/post_migrate/20161221153951_rename_reserved_project_names.rb b/db/post_migrate/20161221153951_rename_reserved_project_names.rb
index 49a6bc884a8320f1fc4e9c335c91ec9161e80952..d322844e2fd99ba157150a446eeedab6c080d8c7 100644
--- a/db/post_migrate/20161221153951_rename_reserved_project_names.rb
+++ b/db/post_migrate/20161221153951_rename_reserved_project_names.rb
@@ -79,17 +79,17 @@ class RenameReservedProjectNames < ActiveRecord::Migration
   private
 
   def reserved_projects
-    Project.unscoped.
-      includes(:namespace).
-      where('EXISTS (SELECT 1 FROM namespaces WHERE projects.namespace_id = namespaces.id)').
-      where('projects.path' => KNOWN_PATHS)
+    Project.unscoped
+      .includes(:namespace)
+      .where('EXISTS (SELECT 1 FROM namespaces WHERE projects.namespace_id = namespaces.id)')
+      .where('projects.path' => KNOWN_PATHS)
   end
 
   def route_exists?(full_path)
     quoted_path = ActiveRecord::Base.connection.quote_string(full_path)
 
-    ActiveRecord::Base.connection.
-      select_all("SELECT id, path FROM routes WHERE path = '#{quoted_path}'").present?
+    ActiveRecord::Base.connection
+      .select_all("SELECT id, path FROM routes WHERE path = '#{quoted_path}'").present?
   end
 
   # Adds number to the end of the path that is not taken by other route
diff --git a/db/post_migrate/20170104150317_requeue_pending_delete_projects.rb b/db/post_migrate/20170104150317_requeue_pending_delete_projects.rb
index f399950bd5e4981e214b31bc0068b922dccc6d82..d7be004d47f9032c8f62c3aea140c79a7f4f2eab 100644
--- a/db/post_migrate/20170104150317_requeue_pending_delete_projects.rb
+++ b/db/post_migrate/20170104150317_requeue_pending_delete_projects.rb
@@ -39,11 +39,11 @@ class RequeuePendingDeleteProjects < ActiveRecord::Migration
 
   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
+    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/post_migrate/20170106142508_fill_authorized_projects.rb b/db/post_migrate/20170106142508_fill_authorized_projects.rb
index 314c8440c8b229135add22caef73f989874cabf2..0ca20587981c10924635d0f0fc759d93298480b9 100644
--- a/db/post_migrate/20170106142508_fill_authorized_projects.rb
+++ b/db/post_migrate/20170106142508_fill_authorized_projects.rb
@@ -15,8 +15,8 @@ class FillAuthorizedProjects < ActiveRecord::Migration
   disable_ddl_transaction!
 
   def up
-    relation = User.select(:id).
-      where('authorized_projects_populated IS NOT TRUE')
+    relation = User.select(:id)
+      .where('authorized_projects_populated IS NOT TRUE')
 
     relation.find_in_batches(batch_size: 1_000) do |rows|
       args = rows.map { |row| [row.id] }
diff --git a/db/post_migrate/20170309171644_reset_relative_position_for_issue.rb b/db/post_migrate/20170309171644_reset_relative_position_for_issue.rb
index b1c9eed114825cfb942fa940e05157fa1977223d..01fff6801831f86c7368a1d52066157a146187d6 100644
--- a/db/post_migrate/20170309171644_reset_relative_position_for_issue.rb
+++ b/db/post_migrate/20170309171644_reset_relative_position_for_issue.rb
@@ -4,6 +4,8 @@ class ResetRelativePositionForIssue < ActiveRecord::Migration
 
   DOWNTIME = false
 
+  disable_ddl_transaction!
+
   def up
     update_column_in_batches(:issues, :relative_position, nil) do |table, query|
       query.where(table[:relative_position].not_eq(nil))
@@ -11,5 +13,6 @@ class ResetRelativePositionForIssue < ActiveRecord::Migration
   end
 
   def down
+    # noop
   end
 end
diff --git a/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb b/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb
index 44c688fa134ca7e9a497d8379c846f6b97c972aa..6a49450cc50d380536924ec37a26a6f4439a33d2 100644
--- a/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb
+++ b/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb
@@ -21,17 +21,17 @@ class RenameMoreReservedProjectNames < ActiveRecord::Migration
   private
 
   def reserved_projects
-    Project.unscoped.
-      includes(:namespace).
-      where('EXISTS (SELECT 1 FROM namespaces WHERE projects.namespace_id = namespaces.id)').
-      where('projects.path' => KNOWN_PATHS)
+    Project.unscoped
+      .includes(:namespace)
+      .where('EXISTS (SELECT 1 FROM namespaces WHERE projects.namespace_id = namespaces.id)')
+      .where('projects.path' => KNOWN_PATHS)
   end
 
   def route_exists?(full_path)
     quoted_path = ActiveRecord::Base.connection.quote_string(full_path)
 
-    ActiveRecord::Base.connection.
-      select_all("SELECT id, path FROM routes WHERE path = '#{quoted_path}'").present?
+    ActiveRecord::Base.connection
+      .select_all("SELECT id, path FROM routes WHERE path = '#{quoted_path}'").present?
   end
 
   # Adds number to the end of the path that is not taken by other route
diff --git a/db/post_migrate/20170317162059_update_upload_paths_to_system.rb b/db/post_migrate/20170317162059_update_upload_paths_to_system.rb
index 9a77b0bbdfb4801f3955aef958e2d8012771a594..ca2912f8dce749dcd1d060143f9af71093096f14 100644
--- a/db/post_migrate/20170317162059_update_upload_paths_to_system.rb
+++ b/db/post_migrate/20170317162059_update_upload_paths_to_system.rb
@@ -7,6 +7,8 @@ class UpdateUploadPathsToSystem < ActiveRecord::Migration
   DOWNTIME = false
   AFFECTED_MODELS = %w(User Project Note Namespace Appearance)
 
+  disable_ddl_transaction!
+
   def up
     update_column_in_batches(:uploads, :path, replace_sql(arel_table[:path], base_directory, new_upload_dir)) do |_table, query|
       query.where(uploads_to_switch_to_new_path)
diff --git a/db/post_migrate/20170324160416_migrate_user_activities_to_users_last_activity_on.rb b/db/post_migrate/20170324160416_migrate_user_activities_to_users_last_activity_on.rb
index 9ad36482c8a3b9d88c1f8aeeae80aa196e3b74e2..397a9a2d28ea6cec8469d0fa3565c0a8a9094ce2 100644
--- a/db/post_migrate/20170324160416_migrate_user_activities_to_users_last_activity_on.rb
+++ b/db/post_migrate/20170324160416_migrate_user_activities_to_users_last_activity_on.rb
@@ -38,11 +38,11 @@ class MigrateUserActivitiesToUsersLastActivityOn < ActiveRecord::Migration
     activities = activities(day.at_beginning_of_day, day.at_end_of_day, page: page)
 
     update_sql =
-      Arel::UpdateManager.new(ActiveRecord::Base).
-        table(users_table).
-        set(users_table[:last_activity_on] => day.to_date).
-        where(users_table[:username].in(activities.map(&:first))).
-        to_sql
+      Arel::UpdateManager.new(ActiveRecord::Base)
+        .table(users_table)
+        .set(users_table[:last_activity_on] => day.to_date)
+        .where(users_table[:username].in(activities.map(&:first)))
+        .to_sql
 
     connection.exec_update(update_sql, self.class.name, [])
 
diff --git a/db/post_migrate/20170406142253_migrate_user_project_view.rb b/db/post_migrate/20170406142253_migrate_user_project_view.rb
index 22f0f2ac200f804418d31606538e7d3c6d628fe4..c4e910b3b44bd37cef461acb485b9702c899312a 100644
--- a/db/post_migrate/20170406142253_migrate_user_project_view.rb
+++ b/db/post_migrate/20170406142253_migrate_user_project_view.rb
@@ -7,6 +7,8 @@ class MigrateUserProjectView < ActiveRecord::Migration
   # Set this constant to true if this migration requires downtime.
   DOWNTIME = false
 
+  disable_ddl_transaction!
+
   def up
     update_column_in_batches(:users, :project_view, 2) do |table, query|
       query.where(table[:project_view].eq(0))
diff --git a/db/post_migrate/20170502070007_enable_auto_cancel_pending_pipelines_for_all.rb b/db/post_migrate/20170502070007_enable_auto_cancel_pending_pipelines_for_all.rb
index 3c13a3d2518b6a43178bc6a7bdfe5e4c565bb23d..765daa0a3470346f8e6c05d35ea6d3efefd5b5ec 100644
--- a/db/post_migrate/20170502070007_enable_auto_cancel_pending_pipelines_for_all.rb
+++ b/db/post_migrate/20170502070007_enable_auto_cancel_pending_pipelines_for_all.rb
@@ -7,6 +7,8 @@ class EnableAutoCancelPendingPipelinesForAll < ActiveRecord::Migration
   DOWNTIME = false
 
   def up
+    disable_statement_timeout
+
     update_column_in_batches(:projects, :auto_cancel_pending_pipelines, 1)
   end
 
diff --git a/db/post_migrate/20170502101023_cleanup_namespaceless_pending_delete_projects.rb b/db/post_migrate/20170502101023_cleanup_namespaceless_pending_delete_projects.rb
index ce52de91cdd502d8028ab9a7e4fd24fbe3815f92..c1e64f201090f3a21ab7da6d910a044c094b8d5d 100644
--- a/db/post_migrate/20170502101023_cleanup_namespaceless_pending_delete_projects.rb
+++ b/db/post_migrate/20170502101023_cleanup_namespaceless_pending_delete_projects.rb
@@ -37,11 +37,11 @@ class CleanupNamespacelessPendingDeleteProjects < ActiveRecord::Migration
 
   def find_batch
     projects = Arel::Table.new(:projects)
-    projects.project(projects[:id]).
-      where(projects[:pending_delete].eq(true)).
-      where(projects[:namespace_id].eq(nil)).
-      skip(@offset * BATCH_SIZE).
-      take(BATCH_SIZE).
-      to_sql
+    projects.project(projects[:id])
+      .where(projects[:pending_delete].eq(true))
+      .where(projects[:namespace_id].eq(nil))
+      .skip(@offset * BATCH_SIZE)
+      .take(BATCH_SIZE)
+      .to_sql
   end
 end
diff --git a/db/post_migrate/20170508170547_add_head_pipeline_for_each_merge_request.rb b/db/post_migrate/20170508170547_add_head_pipeline_for_each_merge_request.rb
index bc3850c0c232d3ca627640ecde5eaf391d0b1cce..f77078ddd70e841e98f690d3302ccd879fb3df88 100644
--- a/db/post_migrate/20170508170547_add_head_pipeline_for_each_merge_request.rb
+++ b/db/post_migrate/20170508170547_add_head_pipeline_for_each_merge_request.rb
@@ -3,17 +3,19 @@ class AddHeadPipelineForEachMergeRequest < ActiveRecord::Migration
 
   DOWNTIME = false
 
+  disable_ddl_transaction!
+
   def up
     disable_statement_timeout
 
     pipelines = Arel::Table.new(:ci_pipelines)
     merge_requests = Arel::Table.new(:merge_requests)
 
-    head_id = pipelines.
-      project(Arel::Nodes::NamedFunction.new('max', [pipelines[:id]])).
-      from(pipelines).
-      where(pipelines[:ref].eq(merge_requests[:source_branch])).
-      where(pipelines[:project_id].eq(merge_requests[:source_project_id]))
+    head_id = pipelines
+      .project(Arel::Nodes::NamedFunction.new('max', [pipelines[:id]]))
+      .from(pipelines)
+      .where(pipelines[:ref].eq(merge_requests[:source_branch]))
+      .where(pipelines[:project_id].eq(merge_requests[:source_project_id]))
 
     sub_query = Arel::Nodes::SqlLiteral.new(Arel::Nodes::Grouping.new(head_id).to_sql)
 
diff --git a/db/post_migrate/20170525140254_rename_all_reserved_paths_again.rb b/db/post_migrate/20170525140254_rename_all_reserved_paths_again.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9441b236c8d2fb30289fb34b52a60688d718fc80
--- /dev/null
+++ b/db/post_migrate/20170525140254_rename_all_reserved_paths_again.rb
@@ -0,0 +1,113 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RenameAllReservedPathsAgain < ActiveRecord::Migration
+  include Gitlab::Database::RenameReservedPathsMigration::V1
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  TOP_LEVEL_ROUTES = %w[
+      -
+      .well-known
+      abuse_reports
+      admin
+      all
+      api
+      assets
+      autocomplete
+      ci
+      dashboard
+      explore
+      files
+      groups
+      health_check
+      help
+      hooks
+      import
+      invites
+      issues
+      jwt
+      koding
+      member
+      merge_requests
+      new
+      notes
+      notification_settings
+      oauth
+      profile
+      projects
+      public
+      repository
+      robots.txt
+      s
+      search
+      sent_notifications
+      services
+      snippets
+      teams
+      u
+      unicorn_test
+      unsubscribes
+      uploads
+      users
+  ].freeze
+
+  PROJECT_WILDCARD_ROUTES = %w[
+      badges
+      blame
+      blob
+      builds
+      commits
+      create
+      create_dir
+      edit
+      environments/folders
+      files
+      find_file
+      gitlab-lfs/objects
+      info/lfs/objects
+      new
+      preview
+      raw
+      refs
+      tree
+      update
+      wikis
+    ].freeze
+
+  GROUP_ROUTES = %w[
+      activity
+      analytics
+      audit_events
+      avatar
+      edit
+      group_members
+      hooks
+      issues
+      labels
+      ldap
+      ldap_group_links
+      merge_requests
+      milestones
+      notification_setting
+      pipeline_quota
+      projects
+      subgroups
+  ].freeze
+
+  def up
+    disable_statement_timeout
+
+    TOP_LEVEL_ROUTES.each { |route| rename_root_paths(route) }
+    PROJECT_WILDCARD_ROUTES.each { |route| rename_wildcard_paths(route) }
+    GROUP_ROUTES.each { |route| rename_child_paths(route) }
+  end
+
+  def down
+    disable_statement_timeout
+
+    revert_renames
+  end
+end
diff --git a/db/post_migrate/20170526185901_remove_stage_id_index_from_builds.rb b/db/post_migrate/20170526185901_remove_stage_id_index_from_builds.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3879cf9133beed23f1d0e7c8f2fcf7278063af45
--- /dev/null
+++ b/db/post_migrate/20170526185901_remove_stage_id_index_from_builds.rb
@@ -0,0 +1,18 @@
+class RemoveStageIdIndexFromBuilds < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def up
+    if index_exists?(:ci_builds, :stage_id)
+      remove_foreign_key(:ci_builds, column: :stage_id)
+      remove_concurrent_index(:ci_builds, :stage_id)
+    end
+  end
+
+  def down
+    # noop
+  end
+end
diff --git a/db/post_migrate/20170526185921_migrate_build_stage_reference.rb b/db/post_migrate/20170526185921_migrate_build_stage_reference.rb
index 797e106cae419de19ce820629b5c2f70f569d4ad..98c32d8284c7b3855b7339c131c4308cc20e5f57 100644
--- a/db/post_migrate/20170526185921_migrate_build_stage_reference.rb
+++ b/db/post_migrate/20170526185921_migrate_build_stage_reference.rb
@@ -3,23 +3,17 @@ class MigrateBuildStageReference < ActiveRecord::Migration
 
   DOWNTIME = false
 
-  def up
-    disable_statement_timeout
-
-    stage_id = Arel.sql <<-SQL.strip_heredoc
-      (SELECT id FROM ci_stages
-         WHERE ci_stages.pipeline_id = ci_builds.commit_id
-           AND ci_stages.name = ci_builds.stage)
-    SQL
+  ##
+  # This is an empty migration, content has been moved to a new one:
+  # post migrate 20170526190000 MigrateBuildStageReferenceAgain
+  #
+  # See gitlab-org/gitlab-ce!12337 for more details.
 
-    update_column_in_batches(:ci_builds, :stage_id, stage_id) do |table, query|
-      query.where(table[:stage_id].eq(nil))
-    end
+  def up
+    # noop
   end
 
   def down
-    disable_statement_timeout
-
-    update_column_in_batches(:ci_builds, :stage_id, nil)
+    # noop
   end
 end
diff --git a/db/post_migrate/20170526190000_migrate_build_stage_reference_again.rb b/db/post_migrate/20170526190000_migrate_build_stage_reference_again.rb
new file mode 100644
index 0000000000000000000000000000000000000000..97cb242415dc3cdddcb33011279b04bd62d0b414
--- /dev/null
+++ b/db/post_migrate/20170526190000_migrate_build_stage_reference_again.rb
@@ -0,0 +1,27 @@
+class MigrateBuildStageReferenceAgain < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def up
+    disable_statement_timeout
+
+    stage_id = Arel.sql <<-SQL.strip_heredoc
+      (SELECT id FROM ci_stages
+         WHERE ci_stages.pipeline_id = ci_builds.commit_id
+           AND ci_stages.name = ci_builds.stage)
+    SQL
+
+    update_column_in_batches(:ci_builds, :stage_id, stage_id) do |table, query|
+      query.where(table[:stage_id].eq(nil))
+    end
+  end
+
+  def down
+    disable_statement_timeout
+
+    update_column_in_batches(:ci_builds, :stage_id, nil)
+  end
+end
diff --git a/db/post_migrate/20170609183112_remove_position_from_issuables.rb b/db/post_migrate/20170609183112_remove_position_from_issuables.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4caaa2e83e8f47b8497ba2b663d205ae40d84b0a
--- /dev/null
+++ b/db/post_migrate/20170609183112_remove_position_from_issuables.rb
@@ -0,0 +1,8 @@
+class RemovePositionFromIssuables < ActiveRecord::Migration
+  DOWNTIME = false
+
+  def change
+    remove_column :issues, :position, :integer
+    remove_column :merge_requests, :position, :integer
+  end
+end
diff --git a/db/post_migrate/20170621102400_add_stage_id_index_to_builds.rb b/db/post_migrate/20170621102400_add_stage_id_index_to_builds.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ac61b5c84a838d06b2e89d5c86e3547a6b71c253
--- /dev/null
+++ b/db/post_migrate/20170621102400_add_stage_id_index_to_builds.rb
@@ -0,0 +1,17 @@
+class AddStageIdIndexToBuilds < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  ##
+  # Improved in 20170703102400_add_stage_id_foreign_key_to_builds.rb
+  #
+
+  def up
+    # noop
+  end
+
+  def down
+    # noop
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 956ca2278f48f424e09c646756dfc56715624a20..40f30a10a016a15e1fcecd38761af0756cba73b3 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: 20170607121233) do
+ActiveRecord::Schema.define(version: 20170703102400) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
@@ -374,9 +374,10 @@ ActiveRecord::Schema.define(version: 20170607121233) do
     t.string "encrypted_value_iv"
     t.integer "project_id", null: false
     t.boolean "protected", default: false, null: false
+    t.string "environment_scope", default: "*", null: false
   end
 
-  add_index "ci_variables", ["project_id"], name: "index_ci_variables_on_project_id", using: :btree
+  add_index "ci_variables", ["project_id", "key", "environment_scope"], name: "index_ci_variables_on_project_id_and_key_and_environment_scope", unique: true, using: :btree
 
   create_table "container_repositories", force: :cascade do |t|
     t.integer "project_id", null: false
@@ -547,7 +548,6 @@ ActiveRecord::Schema.define(version: 20170607121233) do
     t.integer "project_id"
     t.datetime "created_at"
     t.datetime "updated_at"
-    t.integer "position", default: 0
     t.string "branch_name"
     t.text "description"
     t.integer "milestone_id"
@@ -693,6 +693,22 @@ ActiveRecord::Schema.define(version: 20170607121233) do
   add_index "members", ["source_id", "source_type"], name: "index_members_on_source_id_and_source_type", using: :btree
   add_index "members", ["user_id"], name: "index_members_on_user_id", using: :btree
 
+  create_table "merge_request_diff_files", id: false, force: :cascade do |t|
+    t.integer "merge_request_diff_id", null: false
+    t.integer "relative_order", null: false
+    t.boolean "new_file", null: false
+    t.boolean "renamed_file", null: false
+    t.boolean "deleted_file", null: false
+    t.boolean "too_large", null: false
+    t.string "a_mode", null: false
+    t.string "b_mode", null: false
+    t.text "new_path", null: false
+    t.text "old_path", null: false
+    t.text "diff", null: false
+  end
+
+  add_index "merge_request_diff_files", ["merge_request_diff_id", "relative_order"], name: "index_merge_request_diff_files_on_mr_diff_id_and_order", unique: true, using: :btree
+
   create_table "merge_request_diffs", force: :cascade do |t|
     t.string "state"
     t.text "st_commits"
@@ -738,7 +754,6 @@ ActiveRecord::Schema.define(version: 20170607121233) do
     t.integer "target_project_id", null: false
     t.integer "iid"
     t.text "description"
-    t.integer "position", default: 0
     t.datetime "locked_at"
     t.integer "updated_by_id"
     t.text "merge_error"
@@ -756,6 +771,7 @@ ActiveRecord::Schema.define(version: 20170607121233) do
     t.datetime "last_edited_at"
     t.integer "last_edited_by_id"
     t.integer "head_pipeline_id"
+    t.boolean "ref_fetched"
   end
 
   add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
@@ -763,6 +779,7 @@ ActiveRecord::Schema.define(version: 20170607121233) do
   add_index "merge_requests", ["created_at"], name: "index_merge_requests_on_created_at", using: :btree
   add_index "merge_requests", ["deleted_at"], name: "index_merge_requests_on_deleted_at", using: :btree
   add_index "merge_requests", ["description"], name: "index_merge_requests_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
+  add_index "merge_requests", ["head_pipeline_id"], name: "index_merge_requests_on_head_pipeline_id", using: :btree
   add_index "merge_requests", ["milestone_id"], name: "index_merge_requests_on_milestone_id", using: :btree
   add_index "merge_requests", ["source_branch"], name: "index_merge_requests_on_source_branch", using: :btree
   add_index "merge_requests", ["source_project_id"], name: "index_merge_requests_on_source_project_id", using: :btree
@@ -1069,6 +1086,7 @@ ActiveRecord::Schema.define(version: 20170607121233) do
     t.string "import_jid"
     t.integer "cached_markdown_version"
     t.datetime "last_repository_updated_at"
+    t.string "ci_config_path"
   end
 
   add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree
@@ -1532,6 +1550,7 @@ ActiveRecord::Schema.define(version: 20170607121233) do
   add_foreign_key "labels", "namespaces", column: "group_id", on_delete: :cascade
   add_foreign_key "lists", "boards"
   add_foreign_key "lists", "labels"
+  add_foreign_key "merge_request_diff_files", "merge_request_diffs", on_delete: :cascade
   add_foreign_key "merge_request_metrics", "ci_pipelines", column: "pipeline_id", on_delete: :cascade
   add_foreign_key "merge_request_metrics", "merge_requests", on_delete: :cascade
   add_foreign_key "merge_requests_closing_issues", "issues", on_delete: :cascade
diff --git a/doc/README.md b/doc/README.md
index 9f12eed1471a86fc958782d82ebaa5cc9fba38d1..fa755852304d77643d232d6f5470184bffdda24f 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -1,12 +1,24 @@
-# GitLab Community Edition
+# GitLab Documentation
 
-[GitLab](https://about.gitlab.com/) is a Git-based fully featured platform
-for software development.
+Welcome to [GitLab](https://about.gitlab.com/), a Git-based fully featured
+platform for software development!
 
-**GitLab Community Edition (CE)** is an opensource product, self-hosted, free to use.
-All [GitLab products](https://about.gitlab.com/products/) contain the features
-available in GitLab CE. Premium features are available in
-[GitLab Enterprise Edition (EE)](https://about.gitlab.com/gitlab-ee/).
+We offer four different products for you and your company:
+
+- **GitLab Community Edition (CE)** is an [opensource product](https://gitlab.com/gitlab-org/gitlab-ce/),
+self-hosted, free to use. Every feature available in GitLab CE is also available on GitLab Enterprise Edition (Starter and Premium) and GitLab.com.
+- **GitLab Enterprise Edition (EE)** is an [opencore product](https://gitlab.com/gitlab-org/gitlab-ee/),
+self-hosted, fully featured solution of GitLab, available under distinct [subscriptions](https://about.gitlab.com/products/): **GitLab Enterprise Edition Starter (EES)** and **GitLab Enterprise Edition Premium (EEP)**.
+- **GitLab.com**: SaaS GitLab solution, with [free and paid subscriptions](https://about.gitlab.com/gitlab-com/). GitLab.com is hosted by GitLab, Inc., and administrated by GitLab (users don't have access to admin settings).
+
+**GitLab EE** contains all features available in **GitLab CE**,
+plus premium features available in each version: **Enterprise Edition Starter**
+(**EES**) and **Enterprise Edition Premium** (**EEP**). Everything available in
+**EES** is also available in **EEP**.
+
+**Note:** _We are unifying the documentation for CE and EE. To check if certain feature is
+available in CE or EE, look for a note right below the page title containing the GitLab
+version which introduced that feature._
 
 ----
 
@@ -24,7 +36,7 @@ Shortcuts to GitLab's most visited docs:
 - [GitLab Workflow](workflow/README.md): Enhance your workflow with the best of GitLab Workflow.
   - See also [GitLab Workflow - an overview](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/).
 - [GitLab Markdown](user/markdown.md): GitLab's advanced formatting system (GitLab Flavored Markdown).
-- [GitLab Slash Commands](user/project/slash_commands.md): Textual shortcuts for common actions on issues or merge requests that are usually done by clicking buttons or dropdowns in GitLab's UI.
+- [GitLab Quick Actions](user/project/quick_actions.md): Textual shortcuts for common actions on issues or merge requests that are usually done by clicking buttons or dropdowns in GitLab's UI.
 
 ### User account
 
@@ -59,6 +71,7 @@ Manage files and branches from the UI (user interface):
 - Branches
   - [Create a branch](user/project/repository/web_editor.md#create-a-new-branch)
   - [Protected branches](user/project/protected_branches.md#protected-branches)
+  - [Delete merged branches](user/project/repository/branches/index.md#delete-merged-branches)
 
 ### Issues and Merge Requests (MRs)
 
@@ -124,7 +137,7 @@ have access to GitLab administration tools and settings.
 - [Access restrictions](user/admin_area/settings/visibility_and_access_controls.md#enabled-git-access-protocols): Define which Git access protocols can be used to talk to GitLab
 - [Authentication/Authorization](topics/authentication/index.md#gitlab-administrators): Enforce 2FA, configure external authentication with LDAP, SAML, CAS and additional Omniauth providers.
 
-### GitLab admins' superpowers
+### Features
 
 - [Container Registry](administration/container_registry.md): Configure Docker Registry with GitLab.
 - [Custom Git hooks](administration/custom_hooks.md): Custom Git hooks (on the filesystem) for when webhooks aren't enough.
diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md
index 48929910a9c86a78b2dbc39c1c0e26b4de2396b9..332457cb3841a5b2b31e5461e4103aec613b8091 100644
--- a/doc/administration/gitaly/index.md
+++ b/doc/administration/gitaly/index.md
@@ -33,6 +33,145 @@ prometheus_listen_addr = "localhost:9236"
 Changes to `/home/git/gitaly/config.toml` are applied when you run `service
 gitlab restart`.
 
+## Running Gitaly on its own server
+
+> This is an optional way to deploy Gitaly which can benefit GitLab
+installations that are larger than a single machine. Most
+installations will be better served with the default configuration
+used by Omnibus and the GitLab source installation guide.
+
+Starting with GitLab 9.4 it is possible to run Gitaly on a different
+server from the rest of the application. This can improve performance
+when running GitLab with its repositories stored on an NFS server.
+
+At the moment (GitLab 9.4) Gitaly is not yet a replacement for NFS
+because some parts of GitLab still bypass Gitaly when accessing Git
+repositories. If you choose to deploy Gitaly on your NFS server you
+must still also mount your Git shares on your GitLab application
+servers.
+
+Gitaly network traffic is unencrypted so you should use a firewall to
+restrict access to your Gitaly server.
+
+Below we describe how to configure a Gitaly server at address
+`gitaly.internal:9999` with secret token `abc123secret`. We assume
+your GitLab installation has two repository storages, `default` and
+`storage1`.
+
+### Client side token configuration
+
+Start by configuring a token on the client side.
+
+Omnibus installations:
+
+```ruby
+# /etc/gitlab/gitlab.rb
+gitlab_rails['gitaly_token'] = 'abc123secret'
+```
+
+Source installations:
+
+```yaml
+# /home/git/gitlab/config/gitlab.yml
+gitlab:
+  gitaly:
+    token: 'abc123secret'
+```
+
+You need to reconfigure (Omnibus) or restart (source) for these
+changes to be picked up.
+
+### Gitaly server configuration
+
+Next, on the Gitaly server, we need to configure storage paths, enable
+the network listener and configure the token.
+
+Note: if you want to reduce the risk of downtime when you enable
+authentication you can temporarily disable enforcement, see [the
+documentation on configuring Gitaly
+authentication](https://gitlab.com/gitlab-org/gitaly/blob/master/doc/configuration/README.md#authentication)
+.
+
+In most or all cases the storage paths below end in `/repositories`. Check the
+directory layout on your Gitaly server to be sure.
+
+Omnibus installations:
+
+```ruby
+# /etc/gitlab/gitlab.rb
+gitaly['listen_addr'] = '0.0.0.0:9999'
+gitaly['auth_token'] = 'abc123secret'
+gitaly['storage'] = [
+  { 'name' => 'default', 'path' => '/path/to/default/repositories' },
+  { 'name' => 'storage1', 'path' => '/path/to/storage1/repositories' },
+]
+```
+
+Source installations:
+
+```toml
+# /home/git/gitaly/config.toml
+listen_addr = '0.0.0.0:9999'
+
+[auth]
+token = 'abc123secret'
+
+[[storage]
+name = 'default'
+path = '/path/to/default/repositories'
+
+[[storage]]
+name = 'storage1'
+path = '/path/to/storage1/repositories'
+```
+
+Again, reconfigure (Omnibus) or restart (source).
+
+### Converting clients to use the Gitaly server
+
+Now as the final step update the client machines to switch from using
+their local Gitaly service to the new Gitaly server you just
+configured. This is a risky step because if there is any sort of
+network, firewall, or name resolution problem preventing your GitLab
+server from reaching the Gitaly server then all Gitaly requests will
+fail.
+
+We assume that your Gitaly server can be reached at
+`gitaly.internal:9999` from your GitLab server, and that your GitLab
+NFS shares are mounted at `/mnt/gitlab/default` and
+`/mnt/gitlab/storage1` respectively.
+
+Omnibus installations:
+
+```ruby
+# /etc/gitlab/gitlab.rb
+git_data_dirs({
+  { 'default' => { 'path' => '/mnt/gitlab/default', 'gitaly_address' => 'tcp://gitlab.internal:9999' } },
+  { 'storage1' => { 'path' => '/mnt/gitlab/storage1', 'gitaly_address' => 'tcp://gitlab.internal:9999' } },
+})
+```
+
+Source installations:
+
+```yaml
+# /home/git/gitlab/config/gitlab.yml
+gitlab:
+  repositories:
+    storages:
+      default:
+        path: /mnt/gitlab/default/repositories
+        gitaly_address: tcp://gitlab.internal:9999
+      storage1:
+        path: /mnt/gitlab/storage1/repositories
+        gitaly_address: tcp://gitlab.internal:9999
+```
+
+Now reconfigure (Omnibus) or restart (source). When you tail the
+Gitaly logs on your Gitaly server (`sudo gitlab-ctl tail gitaly` or
+`tail -f /home/git/gitlab/log/gitaly.log`) you should see requests
+coming in. One sure way to trigger a Gitaly request is to clone a
+repository from your GitLab server over HTTP.
+
 ## Configuring GitLab to not use Gitaly
 
 Gitaly is still an optional component in GitLab 9.3. This means you
diff --git a/doc/administration/high_availability/nfs.md b/doc/administration/high_availability/nfs.md
index d8e76d6ab94c3ecbf24b7153503374da1786c25b..bd6b7327aedbddd355797dded50f37779b16b6aa 100644
--- a/doc/administration/high_availability/nfs.md
+++ b/doc/administration/high_availability/nfs.md
@@ -1,12 +1,35 @@
 # NFS
 
-## Required NFS Server features
+You can view information and options set for each of the mounted NFS file
+systems by running `sudo nfsstat -m`.
+
+## NFS Server features
+
+### Required features
 
 **File locking**: GitLab **requires** advisory file locking, which is only
 supported natively in NFS version 4. NFSv3 also supports locking as long as
 Linux Kernel 2.6.5+ is used. We recommend using version 4 and do not
 specifically test NFSv3.
 
+### Recommended options
+
+When you define your NFS exports, we recommend you also add the following
+options:
+
+- `no_root_squash` - NFS normally changes the `root` user to `nobody`. This is
+  a good security measure when NFS shares will be accessed by many different
+  users. However, in this case only GitLab will use the NFS share so it
+  is safe. GitLab recommends the `no_root_squash` setting because we need to
+  manage file permissions automatically. Without the setting you may receive
+  errors when the Omnibus package tries to alter permissions. Note that GitLab
+  and other bundled components do **not** run as `root` but as non-privileged
+  users. The recommendation for `no_root_squash` is to allow the Omnibus package
+  to set ownership and permissions on files, as needed.
+- `sync` - Force synchronous behavior. Default is asynchronous and under certain
+  circumstances it could lead to data loss if a failure occurs before data has
+  synced.
+
 ## AWS Elastic File System
 
 GitLab does not recommend using AWS Elastic File System (EFS).
@@ -26,27 +49,10 @@ GitLab does not recommend using EFS with GitLab.
 For more details on another person's experience with EFS, see
 [Amazon's Elastic File System: Burst Credits](https://www.rawkode.io/2017/04/amazons-elastic-file-system-burst-credits/)
 
-### Recommended options
-
-When you define your NFS exports, we recommend you also add the following
-options:
-
-- `no_root_squash` - NFS normally changes the `root` user to `nobody`. This is
-  a good security measure when NFS shares will be accessed by many different
-  users. However, in this case only GitLab will use the NFS share so it
-  is safe. GitLab recommends the `no_root_squash` setting because we need to
-  manage file permissions automatically. Without the setting you may receive
-  errors when the Omnibus package tries to alter permissions. Note that GitLab
-  and other bundled components do **not** run as `root` but as non-privileged
-  users. The recommendation for `no_root_squash` is to allow the Omnibus package
-  to set ownership and permissions on files, as needed.
-- `sync` - Force synchronous behavior. Default is asynchronous and under certain
-  circumstances it could lead to data loss if a failure occurs before data has
-  synced.
-
 ## NFS Client mount options
 
-Below is an example of an NFS mount point we use on GitLab.com:
+Below is an example of an NFS mount point defined in `/etc/fstab` we use on
+GitLab.com:
 
 ```
 10.1.1.1:/var/opt/gitlab/git-data /var/opt/gitlab/git-data nfs4 defaults,soft,rsize=1048576,wsize=1048576,noatime,nobootwait,lookupcache=positive 0 2
diff --git a/doc/administration/high_availability/redis_source.md b/doc/administration/high_availability/redis_source.md
index 3629772b8af31e7dac09990c949f2fe11d9c4937..fe982ea83c2315545e0950a1378c163e0d059c9e 100644
--- a/doc/administration/high_availability/redis_source.md
+++ b/doc/administration/high_availability/redis_source.md
@@ -364,3 +364,4 @@ When in doubt, please read [Redis Sentinel documentation](http://redis.io/topics
 [downloads]: https://about.gitlab.com/downloads
 [restart]: ../restart_gitlab.md#installations-from-source
 [it]: https://gitlab.com/gitlab-org/gitlab-ce/uploads/c4cc8cd353604bd80315f9384035ff9e/The_Internet_IT_Crowd.png
+[resque]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/resque.yml.example
diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md
index 5599435564e647dd2bfb6906db68842d668c4945..3587696225cd77bf2046afa2f0190d60747db934 100644
--- a/doc/administration/job_artifacts.md
+++ b/doc/administration/job_artifacts.md
@@ -46,7 +46,10 @@ To disable artifacts site-wide, follow the steps below.
 After a successful job, GitLab Runner uploads an archive containing the job
 artifacts to GitLab.
 
-To change the location where the artifacts are stored, follow the steps below.
+### Using local storage
+
+To change the location where the artifacts are stored locally, follow the steps
+below.
 
 ---
 
@@ -82,6 +85,13 @@ _The artifacts are stored by default in
 
 1. Save the file and [restart GitLab][] for the changes to take effect.
 
+### Using object storage
+
+In [GitLab Enterprise Edition Premium][eep] you can use an object storage like
+AWS S3 to store the artifacts.
+
+[Learn how to use the object storage option.][ee-os]
+
 ## Expiring artifacts
 
 If an expiry date is used for the artifacts, they are marked for deletion
@@ -148,3 +158,5 @@ memory and disk I/O.
 [reconfigure gitlab]: restart_gitlab.md "How to restart GitLab"
 [restart gitlab]: restart_gitlab.md "How to restart GitLab"
 [gitlab workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse "GitLab Workhorse repository"
+[ee-os]: https://docs.gitlab.com/ee/administration/job_artifacts.html#using-object-storage
+[eep]: https://about.gitlab.com/gitlab-ee/ "GitLab Enterprise Edition Premium"
diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md
new file mode 100644
index 0000000000000000000000000000000000000000..07c05b5a6fb229c0e26262a3264eed426f8537b7
--- /dev/null
+++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md
@@ -0,0 +1,47 @@
+# GitLab Prometheus metrics
+
+>**Note:**
+Available since [Omnibus GitLab 9.3][29118]. Currently experimental. For installations from source
+you'll have to configure it yourself.
+
+GitLab monitors its own internal service metrics, and makes them available at the `/-/metrics` endpoint. Unlike other [Prometheus] exporters, this endpoint requires authentication as it is available on the same URL and port as user traffic.
+
+To enable the GitLab Prometheus metrics:
+
+1. Log into GitLab as an administrator, and go to the Admin area.
+1. Click on the gear, then click on Settings.
+1. Find the `Metrics - Prometheus` section, and click `Enable Prometheus Metrics`
+1. [Restart GitLab][restart] for the changes to take effect
+
+## Collecting the metrics
+
+Since the metrics endpoint is available on the same host and port as other traffic, it requires authentication. The token and URL to access is displayed on the [Health Check][health-check] page.
+
+Currently the embedded Prometheus server is not automatically configured to collect metrics from this endpoint. We recommend setting up another Prometheus server, because the embedded server configuration is overwritten one every reconfigure of GitLab. In the future this will not be required.
+
+## Metrics available
+
+In this experimental phase, only a few metrics are available:
+
+| Metric | Type | Description |
+| ------ | ---- | ----------- |
+| db_ping_timeout | Gauge | Whether or not the last database ping timed out |
+| db_ping_success | Gauge | Whether or not the last database ping succeeded |
+| db_ping_latency | Gauge | Round trip time of the database ping |
+| redis_ping_timeout | Gauge | Whether or not the last redis ping timed out |
+| redis_ping_success | Gauge | Whether or not the last redis ping succeeded |
+| redis_ping_latency | Gauge | Round trip time of the redis ping |
+| filesystem_access_latency | gauge | Latency in accessing a specific filesystem |
+| filesystem_accessible | gauge | Whether or not a specific filesystem is accessible |
+| filesystem_write_latency | gauge | Write latency of a specific filesystem |
+| filesystem_writable | gauge | Whether or not the filesystem is writable |
+| filesystem_read_latency | gauge | Read latency of a specific filesystem |
+| filesystem_readable | gauge | Whether or not the filesystem is readable |
+| user_sessions_logins | Counter | Counter of how many users have logged in | 
+
+[← Back to the main Prometheus page](index.md)
+
+[29118]: https://gitlab.com/gitlab-org/gitlab-ce/issues/29118
+[Prometheus]: https://prometheus.io
+[restart]: ../../restart_gitlab.md#omnibus-gitlab-restart
+[health-check]: ../../../user/admin_area/monitoring/health_check.md
diff --git a/doc/administration/monitoring/prometheus/gitlab_monitor_exporter.md b/doc/administration/monitoring/prometheus/gitlab_monitor_exporter.md
index edb9c911aac45ce2f1c1c540f35385aead6dc290..f68b03d1adec60f849d2c492fef816fc86923804 100644
--- a/doc/administration/monitoring/prometheus/gitlab_monitor_exporter.md
+++ b/doc/administration/monitoring/prometheus/gitlab_monitor_exporter.md
@@ -4,7 +4,7 @@
 Available since [Omnibus GitLab 8.17][1132]. For installations from source
 you'll have to install and configure it yourself.
 
-The [GitLab monitor exporter] allows you to measure various GitLab metrics.
+The [GitLab monitor exporter] allows you to measure various GitLab metrics, pulled from Redis and the database.
 
 To enable the GitLab monitor exporter:
 
diff --git a/doc/administration/monitoring/prometheus/index.md b/doc/administration/monitoring/prometheus/index.md
index b2445d1c0e511178742eda4ffdfcac95b43bd222..695fdf09a87ce7105bacb761a70ba3cdac01201b 100644
--- a/doc/administration/monitoring/prometheus/index.md
+++ b/doc/administration/monitoring/prometheus/index.md
@@ -110,6 +110,14 @@ To disable the monitoring of Kubernetes:
 1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
    take effect
 
+## GitLab Prometheus metrics
+
+> Introduced as an experimental feature in GitLab 9.3.
+
+GitLab monitors its own internal service metrics, and makes them available at the `/-/metrics` endpoint. Unlike other exporters, this endpoint requires authentication as it is available on the same URL and port as user traffic.
+
+[➔ Read more about the GitLab Metrics.](gitlab_metrics.md)
+
 ## Prometheus exporters
 
 There are a number of libraries and servers which help in exporting existing
@@ -143,7 +151,7 @@ The Postgres exporter allows you to measure various PostgreSQL metrics.
 
 ### GitLab monitor exporter
 
-The GitLab monitor exporter allows you to measure various GitLab metrics.
+The GitLab monitor exporter allows you to measure various GitLab metrics, pulled from Redis and the database.
 
 [➔ Read more about the GitLab monitor exporter.](gitlab_monitor_exporter.md)
 
diff --git a/doc/api/README.md b/doc/api/README.md
index 4f189c16673bfc6fdf866874db9c7498a5c348ed..b7f6ee69193535cb2c9be29b5b5f4c57d844c14b 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -29,10 +29,10 @@ following locations:
 - [Labels](labels.md)
 - [Merge Requests](merge_requests.md)
 - [Milestones](milestones.md)
-- [Open source license templates](templates/licenses.md)
 - [Namespaces](namespaces.md)
 - [Notes](notes.md) (comments)
 - [Notification settings](notification_settings.md)
+- [Open source license templates](templates/licenses.md)
 - [Pipelines](pipelines.md)
 - [Pipeline Triggers](pipeline_triggers.md)
 - [Pipeline Schedules](pipeline_schedules.md)
diff --git a/doc/api/branches.md b/doc/api/branches.md
index 325d0ea4ce3699d8dec3d39128675f2921f57661..dfaa7d6fab78338fd93a5e30a6394a70d835060c 100644
--- a/doc/api/branches.md
+++ b/doc/api/branches.md
@@ -251,6 +251,8 @@ curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gi
 
 Will delete all branches that are merged into the project's default branch.
 
+Protected branches will not be deleted as part of this operation.
+
 ```
 DELETE /projects/:id/repository/merged_branches
 ```
diff --git a/doc/api/features.md b/doc/api/features.md
index 89b8d3ac9484720fc2d054305098bf55899f07ad..558869255cccdb98dc21c78f4385fcca2dbf75bd 100644
--- a/doc/api/features.md
+++ b/doc/api/features.md
@@ -58,6 +58,10 @@ POST /features/:name
 | --------- | ---- | -------- | ----------- |
 | `name` | string | yes | Name of the feature to create or update |
 | `value` | integer/string | yes | `true` or `false` to enable/disable, or an integer for percentage of time |
+| `feature_group` | string | no | A Feature group name |
+| `user` | string | no | A GitLab username |
+
+Note that `feature_group` and `user` are mutually exclusive.
 
 ```bash
 curl --data "value=30" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/features/new_library
diff --git a/doc/api/issues.md b/doc/api/issues.md
index 3f949ca5667d6ebc8c795999d5e36288b772f7fe..df5666bb7b6a484a8b38a8872ba50998ed358a80 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -221,7 +221,8 @@ GET /projects/:id/issues?search=issue+title+or+description
 | `order_by`  | string         | no       | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at`                                     |
 | `sort`      | string         | no       | Return requests sorted in `asc` or `desc` order. Default is `desc`                                                          |
 | `search`    | string         | no       | Search project issues against their `title` and `description`                                                                |
-
+| `created_after` | datetime | no | Return issues created after the given time (inclusive) |
+| `created_before` | datetime | no | Return issues created before the given time (inclusive) |
 
 ```bash
 curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index cb22b67f556f549fcf8d62fa269d878bec2e34ed..3dc808c196d68ee870d63c88456d497e6daa0325 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -26,6 +26,8 @@ Parameters:
 | `sort`    | string  | no    | Return requests sorted in `asc` or `desc` order. Default is `desc`  |
 | `milestone`  | string  | no | Return merge requests for a specific milestone |
 | `labels`  | string  | no | Return merge requests matching a comma separated list of labels |
+| `created_after` | datetime | no | Return merge requests created after the given time (inclusive) |
+| `created_before` | datetime | no | Return merge requests created before the given time (inclusive) |
 
 ```json
 [
diff --git a/doc/api/namespaces.md b/doc/api/namespaces.md
index 4ad6071a0ed2f7218d09b8b79fa2550c8d89ad8a..8133251dffe95feed5ccfee5497cb44b008f4102 100644
--- a/doc/api/namespaces.md
+++ b/doc/api/namespaces.md
@@ -29,22 +29,30 @@ Example response:
   {
     "id": 1,
     "path": "user1",
-    "kind": "user"
+    "kind": "user",
+    "full_path": "user1"
   },
   {
     "id": 2,
     "path": "group1",
-    "kind": "group"
+    "kind": "group",
+    "full_path": "group1",
+    "parent_id": "null",
+    "members_count_with_descendants": 2
   },
   {
     "id": 3,
     "path": "bar",
     "kind": "group",
     "full_path": "foo/bar",
+    "parent_id": "9",
+    "members_count_with_descendants": 5
   }
 ]
 ```
 
+**Note**: `members_count_with_descendants` are presented only for group masters/owners.
+
 ## Search for namespace
 
 Get all namespaces that match a string in their name or path.
@@ -72,6 +80,8 @@ Example response:
     "path": "twitter",
     "kind": "group",
     "full_path": "twitter",
+    "parent_id": "null",
+    "members_count_with_descendants": 2
   }
 ]
 ```
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 58f18105e21ffb0db33cd50d6e7dd72530b607d9..c3a49354d0fb284de77d7c0d19c73cfbd831c202 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -261,6 +261,7 @@ Parameters:
   ],
   "only_allow_merge_if_pipeline_succeeds": false,
   "only_allow_merge_if_all_discussions_are_resolved": false,
+  "printing_merge_requests_link_enabled": true,
   "request_access_enabled": false,
   "statistics": {
     "commit_count": 37,
@@ -335,7 +336,7 @@ Parameters:
 | `snippets_enabled` | boolean | no | Enable snippets for this project |
 | `container_registry_enabled` | boolean | no | Enable container registry for this project |
 | `shared_runners_enabled` | boolean | no | Enable shared runners for this project |
-| `visibility` | String | no | See [project visibility level](#project-visibility-level) |
+| `visibility` | string | no | See [project visibility level](#project-visibility-level) |
 | `import_url` | string | no | URL to import repository from |
 | `public_jobs` | boolean | no | If `true`, jobs can be viewed by non-project-members |
 | `only_allow_merge_if_pipeline_succeeds` | boolean | no | Set whether merge requests can only be merged with successful jobs |
@@ -344,6 +345,8 @@ Parameters:
 | `request_access_enabled` | boolean | no | Allow users to request member access |
 | `tag_list`    | array   | no       | The list of tags for a project; put array of tags, that should be finally assigned to a project |
 | `avatar`    | mixed   | no      | Image file for avatar of the project                |
+| `printing_merge_request_link_enabled` | boolean | no | Show link to create/view merge request when pushing from the command line |
+| `ci_config_path` | string | no | The path to CI config file |
 
 ### Create project for user
 
@@ -379,6 +382,8 @@ Parameters:
 | `request_access_enabled` | boolean | no | Allow users to request member access |
 | `tag_list`    | array   | no       | The list of tags for a project; put array of tags, that should be finally assigned to a project |
 | `avatar`    | mixed   | no      | Image file for avatar of the project                |
+| `printing_merge_request_link_enabled` | boolean | no | Show link to create/view merge request when pushing from the command line |
+| `ci_config_path` | string | no | The path to CI config file |
 
 ### Edit project
 
@@ -413,6 +418,7 @@ Parameters:
 | `request_access_enabled` | boolean | no | Allow users to request member access |
 | `tag_list`    | array   | no       | The list of tags for a project; put array of tags, that should be finally assigned to a project |
 | `avatar`    | mixed   | no      | Image file for avatar of the project                |
+| `ci_config_path` | string | no | The path to CI config file |
 
 ### Fork project
 
diff --git a/doc/api/repository_files.md b/doc/api/repository_files.md
index 18ceb8f779e5e5466b62ec551056a2b638f7e864..1fc577561a0aad81a3d158442481946b551cb9c0 100644
--- a/doc/api/repository_files.md
+++ b/doc/api/repository_files.md
@@ -61,7 +61,7 @@ POST /projects/:id/repository/files/:file_path
 ```
 
 ```bash
-curl --request POST --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/app%2Fprojectrb%2E?branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&content=some%20content&commit_message=create%20a%20new%20file'
+curl --request POST --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/files/app%2Fprojectrb%2E?branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&content=some%20content&commit_message=create%20a%20new%20file'
 ```
 
 Example response:
@@ -90,7 +90,7 @@ PUT /projects/:id/repository/files/:file_path
 ```
 
 ```bash
-curl --request PUT --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/app%2Fproject%2Erb?branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&content=some%20other%20content&commit_message=update%20file'
+curl --request PUT --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/files/app%2Fproject%2Erb?branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&content=some%20other%20content&commit_message=update%20file'
 ```
 
 Example response:
@@ -129,7 +129,7 @@ DELETE /projects/:id/repository/files/:file_path
 ```
 
 ```bash
-curl --request DELETE --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/app%2Fproject%2Erb?branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&commit_message=delete%20file'
+curl --request DELETE --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/files/app%2Fproject%2Erb?branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&commit_message=delete%20file'
 ```
 
 Example response:
diff --git a/doc/api/users.md b/doc/api/users.md
index 91ce4f6dac3511c047b4a982e089f7745597ede0..cf09b8f44aa3e528b448fe0d9ae6aeba54a95d24 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -62,6 +62,7 @@ GET /users
     "avatar_url": "http://localhost:3000/uploads/user/avatar/1/index.jpg",
     "web_url": "http://localhost:3000/john_smith",
     "created_at": "2012-05-23T08:00:58Z",
+    "is_admin": false,
     "bio": null,
     "location": null,
     "skype": "",
@@ -94,6 +95,7 @@ GET /users
     "avatar_url": "http://localhost:3000/uploads/user/avatar/2/index.jpg",
     "web_url": "http://localhost:3000/jack_smith",
     "created_at": "2012-05-23T08:01:01Z",
+    "is_admin": false,
     "bio": null,
     "location": null,
     "skype": "",
@@ -197,6 +199,7 @@ Parameters:
   "avatar_url": "http://localhost:3000/uploads/user/avatar/1/index.jpg",
   "web_url": "http://localhost:3000/john_smith",
   "created_at": "2012-05-23T08:00:58Z",
+  "is_admin": false,
   "bio": null,
   "location": null,
   "skype": "",
@@ -251,6 +254,7 @@ Parameters:
 - `can_create_group` (optional) - User can create groups - true or false
 - `confirm` (optional)          - Require confirmation - true (default) or false
 - `external` (optional)         - Flags the user as external - true or false(default)
+- `avatar` (optional)           - Image file for user's avatar
 
 ## User modification
 
@@ -279,6 +283,7 @@ Parameters:
 - `admin` (optional)            - User is admin - true or false (default)
 - `can_create_group` (optional) - User can create groups - true or false
 - `external` (optional)         - Flags the user as external - true or false(default)
+- `avatar` (optional)           - Image file for user's avatar
 
 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,
diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md
index be4dea55c203c17a7167e1d2a454e7cd2175721b..d3433594eb79f147f21e283015eb2199e1a2c0c9 100644
--- a/doc/ci/docker/using_docker_images.md
+++ b/doc/ci/docker/using_docker_images.md
@@ -1,4 +1,4 @@
-# Using Docker Images
+# Using Docker images
 
 GitLab CI in conjunction with [GitLab Runner](../runners/README.md) can use
 [Docker Engine](https://www.docker.com/) to test and build any application.
@@ -17,14 +17,16 @@ can also run on your workstation. The added benefit is that you can test all
 the commands that we will explore later from your shell, rather than having to
 test them on a dedicated CI server.
 
-## Register docker runner
+## Register Docker Runner
 
-To use GitLab Runner with docker you need to register a new runner to use the
-`docker` executor:
+To use GitLab Runner with Docker you need to [register a new Runner][register]
+to use the `docker` executor.
+
+A one-line example can be seen below:
 
 ```bash
-gitlab-ci-multi-runner register \
-  --url "https://gitlab.com/" \
+sudo gitlab-runner register \
+  --url "https://gitlab.example.com/" \
   --registration-token "PROJECT_REGISTRATION_TOKEN" \
   --description "docker-ruby-2.1" \
   --executor "docker" \
@@ -33,26 +35,26 @@ gitlab-ci-multi-runner register \
   --docker-mysql latest
 ```
 
-The registered runner will use the `ruby:2.1` docker image and will run two
+The registered runner will use the `ruby:2.1` Docker image and will run two
 services, `postgres:latest` and `mysql:latest`, both of which will be
 accessible during the build process.
 
 ## What is an image
 
-The `image` keyword is the name of the docker image the docker executor
-will run to perform the CI tasks.  
+The `image` keyword is the name of the Docker image the Docker executor
+will run to perform the CI tasks.
 
-By default the executor will only pull images from [Docker Hub][hub],
+By default, the executor will only pull images from [Docker Hub][hub],
 but this can be configured in the `gitlab-runner/config.toml` by setting
-the [docker pull policy][] to allow using local images.
+the [Docker pull policy][] to allow using local images.
 
 For more information about images and Docker Hub please read
 the [Docker Fundamentals][] documentation.
 
 ## What is a service
 
-The `services` keyword defines just another docker image that is run during
-your job and is linked to the docker image that the `image` keyword defines.
+The `services` keyword defines just another Docker image that is run during
+your job and is linked to the Docker image that the `image` keyword defines.
 This allows you to access the service image during build time.
 
 The service image can run any application, but the most common use case is to
@@ -60,6 +62,11 @@ run a database container, eg. `mysql`. It's easier and faster to use an
 existing image and run it as an additional container than install `mysql` every
 time the project is built.
 
+You are not limited to have only database services. You can add as many
+services you need to `.gitlab-ci.yml` or manually modify `config.toml`.
+Any image found at [Docker Hub][hub] or your private Container Registry can be
+used as a service.
+
 You can see some widely used services examples in the relevant documentation of
 [CI services examples](../services/README.md).
 
@@ -73,22 +80,49 @@ then be used to create a container that is linked to the job container.
 
 The service container for MySQL will be accessible under the hostname `mysql`.
 So, in order to access your database service you have to connect to the host
-named `mysql` instead of a socket or `localhost`.
+named `mysql` instead of a socket or `localhost`. Read more in [accessing the
+services](#accessing-the-services).
 
-## Overwrite image and services
+### Accessing the services
 
-See [How to use other images as services](#how-to-use-other-images-as-services).
+Let's say that you need a Wordpress instance to test some API integration with
+your application.
 
-## How to use other images as services
+You can then use for example the [tutum/wordpress][] image in your
+`.gitlab-ci.yml`:
 
-You are not limited to have only database services. You can add as many
-services you need to `.gitlab-ci.yml` or manually modify `config.toml`.
-Any image found at [Docker Hub][hub] can be used as a service.
+```yaml
+services:
+- tutum/wordpress:latest
+```
+
+If you don't [specify a service alias](#available-settings-for-services-entry),
+when the job is run, `tutum/wordpress` will be started and you will have
+access to it from your build container under two hostnames to choose from:
 
-## Define image and services from `.gitlab-ci.yml`
+- `tutum-wordpress`
+- `tutum__wordpress`
+
+>**Note:**
+Hostnames with underscores are not RFC valid and may cause problems in 3rd party
+applications.
+
+The default aliases for the service's hostname are created from its image name
+following these rules:
+
+- Everything after the colon (`:`) is stripped
+- Slash (`/`) is replaced with double underscores (`__`) and the primary alias
+  is created
+- Slash (`/`) is replaced with a single dash (`-`) and the secondary alias is
+  created (requires GitLab Runner v1.1.0 or higher)
+
+To override the default behavior, you can
+[specify a service alias](#available-settings-for-services-entry).
+
+## Define `image` and `services` from `.gitlab-ci.yml`
 
 You can simply define an image that will be used for all jobs and a list of
-services that you want to use during build time.
+services that you want to use during build time:
 
 ```yaml
 image: ruby:2.2
@@ -125,6 +159,203 @@ test:2.2:
   - bundle exec rake spec
 ```
 
+Or you can pass some [extended configuration options](#extended-docker-configuration-options)
+for `image` and `services`:
+
+```yaml
+image:
+  name: ruby:2.2
+  entrypoint: ["/bin/bash"]
+
+services:
+- name: my-postgres:9.4
+  alias: db-postgres
+  entrypoint: ["/usr/local/bin/db-postgres"]
+  command: ["start"]
+
+before_script:
+- bundle install
+
+test:
+  script:
+  - bundle exec rake spec
+```
+
+## Extended Docker configuration options
+
+> **Note:**
+This feature requires GitLab 9.4 and GitLab Runner 9.4 or higher.
+
+When configuring the `image` or `services` entries, you can use a string or a map as
+options:
+
+- when using a string as an option, it must be the full name of the image to use
+  (including the Registry part if you want to download the image from a Registry
+  other than Docker Hub)
+- when using a map as an option, then it must contain at least the `name`
+  option, which is the same name of the image as used for the string setting
+
+For example, the following two definitions are equal:
+
+1. Using a string as an option to `image` and `services`:
+
+    ```yaml
+    image: "registry.example.com/my/image:latest"
+
+    services:
+    - postgresql:9.4
+    - redis:latest
+    ```
+
+1. Using a map as an option to `image` and `services`. The use of `image:name` is
+   required:
+
+    ```yaml
+    image:
+      name: "registry.example.com/my/image:latest"
+
+    services:
+    - name: postgresql:9.4
+    - name: redis:latest
+    ```
+
+### Available settings for `image`
+
+> **Note:**
+This feature requires GitLab 9.4 and GitLab Runner 9.4 or higher.
+
+| Setting    | Required | Description |
+|------------|----------|-------------|
+| `name`     | yes, when used with any other option      | Full name of the image that should be used. It should contain the Registry part if needed. |
+| `entrypoint` | no     | Command or script that should be executed as the container's entrypoint. It will be translated to Docker's `--entrypoint` option while creating the container. The syntax is similar to [`Dockerfile`'s `ENTRYPOINT`][entrypoint] directive, where each shell token is a separate string in the array. |
+
+### Available settings for `services`
+
+> **Note:**
+This feature requires GitLab 9.4 and GitLab Runner 9.4 or higher.
+
+| Setting    | Required | Description |
+|------------|----------|-------------|
+| `name`       | yes, when used with any other option      | Full name of the image that should be used. It should contain the Registry part if needed. |
+| `entrypoint` | no     | Command or script that should be executed as the container's entrypoint. It will be translated to Docker's `--entrypoint` option while creating the container. The syntax is similar to [`Dockerfile`'s `ENTRYPOINT`][entrypoint] directive, where each shell token is a separate string in the array. |
+| `command`    | no       | Command or script that should be used as the container's command. It will be translated to arguments passed to Docker after the image's name. The syntax is similar to [`Dockerfile`'s `CMD`][cmd] directive, where each shell token is a separate string in the array. |
+| `alias`      | no       | Additional alias that can be used to access the service from the job's container. Read [Accessing the services](#accessing-the-services) for more information. |
+
+### Starting multiple services from the same image
+
+Before the new extended Docker configuration options, the following configuration
+would not work properly:
+
+```yaml
+services:
+- mysql:latest
+- mysql:latest
+```
+
+The Runner would start two containers using the `mysql:latest` image, but both
+of them would be added to the job's container with the `mysql` alias based on
+the [default hostname naming](#accessing-the-services). This would end with one
+of the services not being accessible.
+
+After the new extended Docker configuration options, the above example would
+look like:
+
+```yaml
+services:
+- name: mysql:latest
+  alias: mysql-1
+- name: mysql:latest
+  alias: mysql-2
+```
+
+The Runner will still start two containers using the `mysql:latest` image,
+but now each of them will also be accessible with the alias configured
+in `.gitlab-ci.yml` file.
+
+### Setting a command for the service
+
+Let's assume you have a `super/sql:latest` image with some SQL database
+inside it and you would like to use it as a service for your job. Let's also
+assume that this image doesn't start the database process while starting
+the container and the user needs to manually use `/usr/bin/super-sql run` as
+a command to start the database.
+
+Before the new extended Docker configuration options, you would need to create
+your own image based on the `super/sql:latest` image, add the default command,
+and then use it in job's configuration, like:
+
+```Dockerfile
+# my-super-sql:latest image's Dockerfile
+
+FROM super/sql:latest
+CMD ["/usr/bin/super-sql", "run"]
+```
+
+```yaml
+# .gitlab-ci.yml
+
+services:
+- my-super-sql:latest
+```
+
+After the new extended Docker configuration options, you can now simply
+set a `command` in `.gitlab-ci.yml`, like:
+
+```yaml
+# .gitlab-ci.yml
+
+services:
+- name: super/sql:latest
+  command: ["/usr/bin/super-sql", "run"]
+```
+
+As you can see, the syntax of `command` is similar to [Dockerfile's `CMD`][cmd].
+
+### Overriding the entrypoint of an image
+
+Let's assume you have a `super/sql:experimental` image with some SQL database
+inside it and you would like to use it as a base image for your job because you
+want to execute some tests with this database binary. Let's also assume that
+this image is configured with `/usr/bin/super-sql run` as an entrypoint. That
+means, that when starting the container without additional options, it will run
+the database's process, while Runner expects that the image will have no
+entrypoint or at least will start with a shell as its entrypoint.
+
+Previously we would need to create our own image based on the
+`super/sql:experimental` image, set the entrypoint to a shell, and then use
+it in job's configuration, e.g.:
+
+Before the new extended Docker configuration options, you would need to create
+your own image based on the `super/sql:experimental` image, set the entrypoint
+to a shell and then use it in job's configuration, like:
+
+```Dockerfile
+# my-super-sql:experimental image's Dockerfile
+
+FROM super/sql:experimental
+ENTRYPOINT ["/bin/sh"]
+```
+
+```yaml
+# .gitlab-ci.yml
+
+image: my-super-sql:experimental
+```
+
+After the new extended Docker configuration options, you can now simply
+set an `entrypoint` in `.gitlab-ci.yml`, like:
+
+```yaml
+# .gitlab-ci.yml
+
+image:
+  name: super/sql:experimental
+  entrypoint: ["/bin/sh"]
+```
+
+As you can see the syntax of `entrypoint` is similar to
+[Dockerfile's `ENTRYPOINT`][entrypoint].
+
 ## Define image and services in `config.toml`
 
 Look for the `[runners.docker]` section:
@@ -138,7 +369,7 @@ Look for the `[runners.docker]` section:
 The image and services defined this way will be added to all job run by
 that runner.
 
-## Define an image from a private Docker registry
+## Define an image from a private Container Registry
 
 > **Notes:**
 - This feature requires GitLab Runner **1.8** or higher
@@ -193,44 +424,6 @@ To configure access for `registry.example.com`, follow these steps:
 You can add configuration for as many registries as you want, adding more
 registries to the `"auths"` hash as described above.
 
-## Accessing the services
-
-Let's say that you need a Wordpress instance to test some API integration with
-your application.
-
-You can then use for example the [tutum/wordpress][] image in your
-`.gitlab-ci.yml`:
-
-```yaml
-services:
-- tutum/wordpress:latest
-```
-
-When the job is run, `tutum/wordpress` will be started and you will have
-access to it from your build container under the hostnames `tutum-wordpress`
-(requires GitLab Runner v1.1.0 or newer) and `tutum__wordpress`.
-
-When using a private registry, the image name also includes a hostname and port
-of the registry. 
-
-```yaml
-services:
-- docker.example.com:5000/wordpress:latest
-```
-
-The service hostname will also include the registry hostname. Service will be
-available under hostnames `docker.example.com-wordpress` (requires GitLab Runner v1.1.0 or newer)
-and `docker.example.com__wordpress`.
-
-*Note: hostname with underscores is not RFC valid and may cause problems in 3rd party applications.*
-
-The alias hostnames for the service are made from the image name following these
-rules:
-
-1. Everything after `:` is stripped
-2. Slash (`/`) is replaced with double underscores (`__`) - primary alias
-3. Slash (`/`) is replaced with dash (`-`) - secondary alias, requires GitLab Runner v1.1.0 or newer
-
 ## Configuring services
 
 Many services accept environment variables which allow you to easily change
@@ -257,7 +450,7 @@ See the specific documentation for
 
 ## How Docker integration works
 
-Below is a high level overview of the steps performed by docker during job
+Below is a high level overview of the steps performed by Docker during job
 time.
 
 1. Create any service container: `mysql`, `postgresql`, `mongodb`, `redis`.
@@ -274,7 +467,7 @@ time.
 ## How to debug a job locally
 
 *Note: The following commands are run without root privileges. You should be
-able to run docker with your regular user account.*
+able to run Docker with your regular user account.*
 
 First start with creating a file named `build_script`:
 
@@ -334,3 +527,6 @@ creation.
 [mysql-hub]: https://hub.docker.com/r/_/mysql/
 [runner-priv-reg]: http://docs.gitlab.com/runner/configuration/advanced-configuration.html#using-a-private-container-registry
 [secret variable]: ../variables/README.md#secret-variables
+[entrypoint]: https://docs.docker.com/engine/reference/builder/#entrypoint
+[cmd]: https://docs.docker.com/engine/reference/builder/#cmd
+[register]: https://docs.gitlab.com/runner/register/
diff --git a/doc/ci/examples/deployment/composer-npm-deploy.md b/doc/ci/examples/deployment/composer-npm-deploy.md
index 8b0d8a003fdc6abe83497a805c06a402d079bcf7..b9f0485290ed5fd7735da14452524ef14a85f1bf 100644
--- a/doc/ci/examples/deployment/composer-npm-deploy.md
+++ b/doc/ci/examples/deployment/composer-npm-deploy.md
@@ -20,12 +20,12 @@ before_script:
   - 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:
+This will make sure we have all requirements ready. Next, we want to run `composer install` 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
+  - php composer.phar install
   - npm install
   - npm run deploy
 ```
@@ -133,7 +133,7 @@ before_script:
   - php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
   - php composer-setup.php
   - php -r "unlink('composer-setup.php');"
-  - php composer.phar update
+  - php composer.phar install
   - npm install
   - npm run deploy
   - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md
index 41cae58782d2aa1dd9f07d6becb072c38e329c8f..88e53ff40e884ecd2b9a361aeb9d938b56442685 100644
--- a/doc/ci/quick_start/README.md
+++ b/doc/ci/quick_start/README.md
@@ -155,7 +155,7 @@ Find more information about different Runners in the
 [Runners](../runners/README.md) documentation.
 
 You can find whether any Runners are assigned to your project by going to
-**Settings ➔ CI/CD Pipelines**. Setting up a Runner is easy and straightforward. The
+**Settings ➔ Pipelines**. Setting up a Runner is easy and straightforward. The
 official Runner supported by GitLab is written in Go and its documentation
 can be found at <https://docs.gitlab.com/runner/>.
 
@@ -168,7 +168,7 @@ Follow the links above to set up your own Runner or use a Shared Runner as
 described in the next section.
 
 Once the Runner has been set up, you should see it on the Runners page of your
-project, following **Settings ➔ CI/CD Pipelines**.
+project, following **Settings ➔ Pipelines**.
 
 ![Activated runners](img/runners_activated.png)
 
@@ -181,7 +181,7 @@ These are special virtual machines that run on GitLab's infrastructure and can
 build any project.
 
 To enable the **Shared Runners** you have to go to your project's
-**Settings ➔ CI/CD Pipelines** and click **Enable shared runners**.
+**Settings ➔ Pipelines** and click **Enable shared runners**.
 
 [Read more on Shared Runners](../runners/README.md).
 
diff --git a/doc/ci/ssh_keys/README.md b/doc/ci/ssh_keys/README.md
index befaa06e9184368d92f2eb9c9e9b1d8ff7c7834c..cf25a8b618f7e7e0c67358797fb59292d6e87151 100644
--- a/doc/ci/ssh_keys/README.md
+++ b/doc/ci/ssh_keys/README.md
@@ -34,9 +34,9 @@ instructions to [generate an SSH key](../../ssh/README.md). Do not add a
 passphrase to the SSH key, or the `before_script` will prompt for it.
 
 Then, create a new **Secret Variable** in your project settings on GitLab
-following **Settings > Variables**. As **Key** add the name `SSH_PRIVATE_KEY`
-and in the **Value** field paste the content of your _private_ key that you
-created earlier.
+following **Settings > Pipelines** and look for the "Secret Variables" section.
+As **Key** add the name `SSH_PRIVATE_KEY` and in the **Value** field paste the 
+content of your _private_ key that you created earlier.
 
 It is also good practice to check the server's own public key to make sure you
 are not being targeted by a man-in-the-middle attack. To do this, add another
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index d1f9881e51b3dbfd7c17c8695872ef678b6bbf79..ee23ac0adbee12ced39ae0809156171c7482dc23 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -37,9 +37,10 @@ future GitLab releases.**
 |-------------------------------- |--------|--------|-------------|
 | **CI**                          | all    | 0.4    | Mark that job is executed in CI environment |
 | **CI_COMMIT_REF_NAME**          | 9.0    | all    | The branch or tag name for which project is built |
-| **CI_COMMIT_REF_SLUG**          | 9.0    | all    | `$CI_COMMIT_REF_NAME` lowercased, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. Use in URLs and domain names. |
+| **CI_COMMIT_REF_SLUG**          | 9.0    | all    | `$CI_COMMIT_REF_NAME` lowercased, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. No leading / trailing `-`. Use in URLs, host names and domain names. |
 | **CI_COMMIT_SHA**               | 9.0    | all    | The commit revision for which project is built |
 | **CI_COMMIT_TAG**               | 9.0    | 0.5    | The commit tag name. Present only when building tags. |
+| **CI_CONFIG_PATH**              | 9.4    | 0.5    | The path to CI config file. Defaults to `.gitlab-ci.yml` |
 | **CI_DEBUG_TRACE**              | all    | 1.7    | Whether [debug tracing](#debug-tracing) is enabled |
 | **CI_ENVIRONMENT_NAME**         | 8.15   | all    | The name of the environment for this job |
 | **CI_ENVIRONMENT_SLUG**         | 8.15   | all    | A simplified version of the environment name, suitable for inclusion in DNS, URLs, Kubernetes labels, etc. |
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 8a0662db6fd0544694e6c6cdd8c098a05434b4b1..724843a4d565dd58c660d3fc26c7b80a72da577c 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -306,6 +306,53 @@ cache:
   untracked: true
 ```
 
+### cache:policy
+
+> Introduced in GitLab 9.4.
+
+The default behaviour of a caching job is to download the files at the start of
+execution, and to re-upload them at the end. This allows any changes made by the
+job to be persisted for future runs, and is known as the `pull-push` cache
+policy.
+
+If you know the job doesn't alter the cached files, you can skip the upload step
+by setting `policy: pull` in the job specification. Typically, this would be
+twinned with an ordinary cache job at an earlier stage to ensure the cache
+is updated from time to time:
+
+```yaml
+stages:
+  - setup
+  - test
+
+prepare:
+  stage: setup
+  cache:
+    key: gems
+    paths:
+      - vendor/bundle
+  script:
+    - bundle install --deployment
+
+rspec:
+  stage: test
+  cache:
+    key: gems
+    paths:
+      - vendor/bundle
+    policy: pull
+  script:
+    - bundle exec rspec ...
+```
+
+This helps to speed up job execution and reduce load on the cache server,
+especially when you have a large number of cache-using jobs executing in
+parallel.
+
+Additionally, if you have a job that unconditionally recreates the cache without
+reference to its previous contents, you can use `policy: push` in that job to
+skip the download step.
+
 ## Jobs
 
 `.gitlab-ci.yml` allows you to specify an unlimited number of jobs. Each job
diff --git a/doc/development/README.md b/doc/development/README.md
index 9496a87d84d3e610da46da10560cbd51ede152c8..a2a07c37ced5047a75a0774e04c11bf262554a41 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -54,6 +54,7 @@
 - [Polymorphic Associations](polymorphic_associations.md)
 - [Single Table Inheritance](single_table_inheritance.md)
 - [Background Migrations](background_migrations.md)
+- [Storing SHA1 Hashes As Binary](sha1_as_binary.md)
 
 ## i18n
 
diff --git a/doc/development/architecture.md b/doc/development/architecture.md
index acd5e3c20937f463cf6f84aff2658fb958c6f713..54029e005071c0bf275735c9a06356b17f60714d 100644
--- a/doc/development/architecture.md
+++ b/doc/development/architecture.md
@@ -194,3 +194,7 @@ bundle exec rake gitlab:check RAILS_ENV=production
 ```
 
 Note: It is recommended to log into the `git` user using `sudo -i -u git` or `sudo su - git`. While the sudo commands provided by gitlabhq work in Ubuntu they do not always work in RHEL.
+
+## GitLab.com
+
+We've also detailed [our architecture of GitLab.com](https://about.gitlab.com/handbook/infrastructure/production-architecture/) but this is probably over the top unless you have millions of users.
diff --git a/doc/development/fe_guide/style_guide_js.md b/doc/development/fe_guide/style_guide_js.md
index d2d895172410fa41d926e4e3bc7cdb4a3601fb0c..ae844fa105161d4b1cc90ec8579cbc917d77f086 100644
--- a/doc/development/fe_guide/style_guide_js.md
+++ b/doc/development/fe_guide/style_guide_js.md
@@ -463,20 +463,24 @@ A forEach will cause side effects, it will be mutating the array being iterated.
   1. `destroyed`
 
 #### Vue and Boostrap
-1. Tooltips: Do not rely on `has-tooltip` class name for vue components
+1. Tooltips: Do not rely on `has-tooltip` class name for Vue components
   ```javascript
     // bad
-    <span class="has-tooltip">
+    <span
+      class="has-tooltip"
+      title="Some tooltip text">
       Text
     </span>
 
     // good
-    <span data-toggle="tooltip">
+    <span
+      v-tooltip
+      title="Some tooltip text">
       Text
     </span>
   ```
 
-1. Tooltips: When using a tooltip, include the tooltip mixin
+1. Tooltips: When using a tooltip, include the tooltip directive, `./app/assets/javascripts/vue_shared/directives/tooltip.js`
 
 1. Don't change `data-original-title`.
   ```javascript
diff --git a/doc/development/limit_ee_conflicts.md b/doc/development/limit_ee_conflicts.md
index 51b4b398f2ca8769721ba1fbc79dc2265cbd5907..899be9eae4b1350e8bcc95722ec6a0c57c49e2c8 100644
--- a/doc/development/limit_ee_conflicts.md
+++ b/doc/development/limit_ee_conflicts.md
@@ -166,8 +166,8 @@ For instance this kind of thing:
       = render 'projects/zen', f: form, attr: :description,
                                classes: 'note-textarea',
                                placeholder: "Write a comment or drag your files here...",
-                               supports_slash_commands: !issuable.persisted?
-      = render 'projects/notes/hints', supports_slash_commands: !issuable.persisted?
+                               supports_quick_actions: !issuable.persisted?
+      = render 'projects/notes/hints', supports_quick_actions: !issuable.persisted?
       .clearfix
       .error-alert
 - if issuable.is_a?(Issue)
diff --git a/doc/development/policies.md b/doc/development/policies.md
new file mode 100644
index 0000000000000000000000000000000000000000..62141356f59caa9f006fcd6846646ec341334062
--- /dev/null
+++ b/doc/development/policies.md
@@ -0,0 +1,116 @@
+# `DeclarativePolicy` framework
+
+The DeclarativePolicy framework is designed to assist in performance of policy checks, and to enable ease of extension for EE. The DSL code in `app/policies` is what `Ability.allowed?` uses to check whether a particular action is allowed on a subject.
+
+The policy used is based on the subject's class name - so `Ability.allowed?(user, :some_ability, project)` will create a `ProjectPolicy` and check permissions on that.
+
+## Managing Permission Rules
+
+Permissions are broken into two parts: `conditions` and `rules`. Conditions are boolean expressions that can access the database and the environment, while rules are statically configured combinations of expressions and other rules that enable or prevent certain abilities. For an ability to be allowed, it must be enabled by at least one rule, and not prevented by any.
+
+
+### Conditions
+
+Conditions are defined by the `condition` method, and are given a name and a block. The block will be executed in the context of the policy object - so it can access `@user` and `@subject`, as well as call any methods defined on the policy. Note that `@user` may be nil (in the anonymous case), but `@subject` is guaranteed to be a real instance of the subject class.
+
+``` ruby
+class FooPolicy < BasePolicy
+  condition(:is_public) do
+    # @subject guaranteed to be an instance of Foo
+    @subject.public?
+  end
+
+  # instance methods can be called from the condition as well
+  condition(:thing) { check_thing }
+
+  def check_thing
+    # ...
+  end
+end
+```
+
+When you define a condition, a predicate method is defined on the policy to check whether that condition passes - so in the above example, an instance of `FooPolicy` will also respond to `#is_public?` and `#thing?`.
+
+Conditions are cached according to their scope. Scope and ordering will be covered later.
+
+### Rules
+
+A `rule` is a logical combination of conditions and other rules, that are configured to enable or prevent certain abilities. It is important to note that the rule configuration is static - a rule's logic cannot touch the database or know about `@user` or `@subject`. This allows us to cache only at the condition level. Rules are specified through the `rule` method, which takes a block of DSL configuration, and returns an object that responds to `#enable` or `#prevent`:
+
+``` ruby
+class FooPolicy < BasePolicy
+  # ...
+
+  rule { is_public }.enable :read
+  rule { thing }.prevent :read
+
+  # equivalently,
+  rule { is_public }.policy do
+    enable :read
+  end
+
+  rule { ~thing }.policy do
+    prevent :read
+  end
+end
+```
+
+Within the rule DSL, you can use:
+
+* A regular word mentions a condition by name - a rule that is in effect when that condition is truthy.
+* `~` indicates negation
+* `&` and `|` are logical combinations, also available as `all?(...)` and `any?(...)`
+* `can?(:other_ability)` delegates to the rules that apply to `:other_ability`. Note that this is distinct from the instance method `can?`, which can check dynamically - this only configures a delegation to another ability.
+
+## Scores, Order, Performance
+
+To see how the rules get evaluated into a judgment, it is useful in a console to use `policy.debug(:some_ability)`. This will print the rules in the order they are evaluated.
+
+When a policy is asked whether a particular ability is allowed (`policy.allowed?(:some_ability)`), it does not necessarily have to compute all the conditions on the policy. First, only the rules relevant to that particular ability are selected. Then, the execution model takes advantage of short-circuiting, and attempts to sort rules based on a heuristic of how expensive they will be to calculate. The sorting is dynamic and cache-aware, so that previously calculated conditions will be considered first, before computing other conditions.
+
+## Scope
+
+Sometimes, a condition will only use data from `@user` or only from `@subject`. In this case, we want to change the scope of the caching, so that we don't recalculate conditions unnecessarily. For example, given:
+
+``` ruby
+class FooPolicy < BasePolicy
+  condition(:expensive_condition) { @subject.expensive_query? }
+
+  rule { expensive_condition }.enable :some_ability
+end
+```
+
+Naively, if we call `Ability.can?(user1, :some_ability, foo)` and `Ability.can?(user2, :some_ability, foo)`, we would have to calculate the condition twice - since they are for different users. But if we use the `scope: :subject` option:
+
+``` ruby
+  condition(:expensive_condition, scope: :subject) { @subject.expensive_query? }
+```
+
+then the result of the condition will be cached globally only based on the subject - so it will not be calculated repeatedly for different users. Similarly, `scope: :user` will cache only based on the user.
+
+**DANGER**: If you use a `:scope` option when the condition actually uses data from
+both user and subject (including a simple anonymous check!) your result will be cached at too global of a scope and will result in cache bugs.
+
+Sometimes we are checking permissions for a lot of users for one subject, or a lot of subjects for one user. In this case, we want to set a *preferred scope* - i.e. tell the system that we prefer rules that can be cached on the repeated parameter. For example, in `Ability.users_that_can_read_project`:
+
+``` ruby
+def users_that_can_read_project(users, project)
+  DeclarativePolicy.subject_scope do
+    users.select { |u| allowed?(u, :read_project, project) }
+  end
+end
+```
+
+This will, for example, prefer checking `project.public?` to checking `user.admin?`.
+
+## Delegation
+
+Delegation is the inclusion of rules from another policy, on a different subject. For example,
+
+``` ruby
+class FooPolicy < BasePolicy
+  delegate { @subject.project }
+end
+```
+
+will include all rules from `ProjectPolicy`. The delegated conditions will be evaluated with the correct delegated subject, and will be sorted along with the regular rules in the policy. Note that only the relevant rules for a particular ability will actually be considered.
diff --git a/doc/development/sha1_as_binary.md b/doc/development/sha1_as_binary.md
new file mode 100644
index 0000000000000000000000000000000000000000..3151cc29bbc67decfb378268ce5f99a36c089151
--- /dev/null
+++ b/doc/development/sha1_as_binary.md
@@ -0,0 +1,36 @@
+# Storing SHA1 Hashes As Binary
+
+Storing SHA1 hashes as strings is not very space efficient. A SHA1 as a string
+requires at least 40 bytes, an additional byte to store the encoding, and
+perhaps more space depending on the internals of PostgreSQL and MySQL.
+
+On the other hand, if one were to store a SHA1 as binary one would only need 20
+bytes for the actual SHA1, and 1 or 4 bytes of additional space (again depending
+on database internals). This means that in the best case scenario we can reduce
+the space usage by 50%.
+
+To make this easier to work with you can include the concern `ShaAttribute` into
+a model and define a SHA attribute using the `sha_attribute` class method. For
+example:
+
+```ruby
+class Commit < ActiveRecord::Base
+  include ShaAttribute
+
+  sha_attribute :sha
+end
+```
+
+This allows you to use the value of the `sha` attribute as if it were a string,
+while storing it as binary. This means that you can do something like this,
+without having to worry about converting data to the right binary format:
+
+```ruby
+commit = Commit.find_by(sha: '88c60307bd1f215095834f09a1a5cb18701ac8ad')
+commit.sha = '971604de4cfa324d91c41650fabc129420c8d1cc'
+commit.save
+```
+
+There is however one requirement: the column used to store the SHA has _must_ be
+a binary type. For Rails this means you need to use the `:binary` type instead
+of `:text` or `:string`.
diff --git a/doc/install/database_mysql.md b/doc/install/database_mysql.md
index 9a171d346714d57a5b84f2a66d6e555ee063e54d..37e9b3101ca01d23f9982658e274eacfcef35bee 100644
--- a/doc/install/database_mysql.md
+++ b/doc/install/database_mysql.md
@@ -43,7 +43,7 @@ mysql> SET GLOBAL innodb_file_per_table=1, innodb_file_format=Barracuda, innodb_
 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';
+mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER, LOCK TABLES, REFERENCES, TRIGGER ON `gitlabhq_production`.* TO 'git'@'localhost';
 
 # Quit the database session
 mysql> \q
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 84af6432889ddd0009a5268b61378297c34ac529..992ff162efb706e939d21c59af3e6c19c134d92c 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -294,9 +294,9 @@ sudo usermod -aG redis git
 ### Clone the Source
 
     # Clone GitLab repository
-    sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 9-2-stable gitlab
+    sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 9-3-stable gitlab
 
-**Note:** You can change `9-2-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
+**Note:** You can change `9-3-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
 
 ### Configure It
 
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index 197a92905c80862b005f034ab8ccec5340cff80b..a3d676433e6b11fd207279332f0cb7c9823ed1a0 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -86,56 +86,32 @@ if your available memory changes.
 
 Notice: The 25 workers of Sidekiq will show up as separate processes in your process overview (such as top or htop) but they share the same RAM allocation since Sidekiq is a multithreaded application. Please see the section below about Unicorn workers for information about many you need of those.
 
-## GitLab Runner
-
-We strongly advise against installing GitLab Runner on the same machine you plan
-to install GitLab on. Depending on how you decide to configure GitLab Runner and
-what tools you use to exercise your application in the CI environment, GitLab
-Runner can consume significant amount of available memory.
-
-Memory consumption calculations, that are available above, will not be valid if
-you decide to run GitLab Runner and the GitLab Rails application on the same
-machine.
-
-It is also not safe to install everything on a single machine, because of the
-[security reasons] - especially when you plan to use shell executor with GitLab
-Runner.
-
-We recommend using a separate machine for each GitLab Runner, if you plan to
-use the CI features.
-
-[security reasons]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/blob/master/docs/security/index.md
-
-## Unicorn Workers
-
-It's possible to increase the amount of unicorn workers and this will usually help to reduce the response time of the applications and increase the ability to handle parallel requests.
-
-For most instances we recommend using: CPU cores + 1 = unicorn workers.
-So for a machine with 2 cores, 3 unicorn workers is ideal.
+## Database
 
-For all machines that have 2GB and up we recommend a minimum of three unicorn workers.
-If you have a 1GB machine we recommend to configure only two Unicorn workers to prevent excessive swapping.
+The server running the database should have _at least_ 5-10 GB of storage
+available, though the exact requirements depend on the size of the GitLab
+installation (e.g. the number of users, projects, etc).
 
-To change the Unicorn workers when you have the Omnibus package please see [the Unicorn settings in the Omnibus GitLab documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/unicorn.md#unicorn-settings).
+We currently support the following databases:
 
-## Database
+- PostgreSQL (highly recommended)
+- MySQL/MariaDB (strongly discouraged, not all GitLab features are supported, no support for [MySQL/MariaDB GTID](https://mariadb.com/kb/en/mariadb/gtid/))
 
-We currently support the following databases:
+We highly recommend the use of PostgreSQL instead of MySQL/MariaDB as not all
+features of GitLab work with MySQL/MariaDB:
 
-- PostgreSQL
-- MySQL/MariaDB
+1. MySQL support for subgroups was [dropped with GitLab 9.3][post].
+   See [issue #30472][30472] for more information.
+1. GitLab Geo does [not support MySQL](https://docs.gitlab.com/ee/gitlab-geo/database.html#mysql-replication).
+1. [Zero downtime migrations][zero] do not work with MySQL
+1. We expect this list to grow over time.
 
-We _highly_ recommend the use of PostgreSQL instead of MySQL/MariaDB as not all
-features of GitLab may work with MySQL/MariaDB. For example, MySQL does not have
-the right features to support nested groups in an efficient manner; see
-<https://gitlab.com/gitlab-org/gitlab-ce/issues/30472> for more information
-about this. GitLab Geo also does [not support MySQL](https://docs.gitlab.com/ee/gitlab-geo/database.html#mysql-replication).
 Existing users using GitLab with MySQL/MariaDB are advised to
-migrate to PostgreSQL instead.
+[migrate to PostgreSQL](../update/mysql_to_postgresql.md) instead.
 
-The server running the database should have _at least_ 5-10 GB of storage
-available, though the exact requirements depend on the size of the GitLab
-installation (e.g. the number of users, projects, etc).
+[30472]: https://gitlab.com/gitlab-org/gitlab-ce/issues/30472
+[zero]: ../update/README.md#upgrading-without-downtime
+[post]: https://about.gitlab.com/2017/06/22/gitlab-9-3-released/#dropping-support-for-subgroups-in-mysql
 
 ### PostgreSQL Requirements
 
@@ -154,6 +130,18 @@ CREATE EXTENSION pg_trgm;
 On some systems you may need to install an additional package (e.g.
 `postgresql-contrib`) for this extension to become available.
 
+## Unicorn Workers
+
+It's possible to increase the amount of unicorn workers and this will usually help to reduce the response time of the applications and increase the ability to handle parallel requests.
+
+For most instances we recommend using: CPU cores + 1 = unicorn workers.
+So for a machine with 2 cores, 3 unicorn workers is ideal.
+
+For all machines that have 2GB and up we recommend a minimum of three unicorn workers.
+If you have a 1GB machine we recommend to configure only two Unicorn workers to prevent excessive swapping.
+
+To change the Unicorn workers when you have the Omnibus package please see [the Unicorn settings in the Omnibus GitLab documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/unicorn.md#unicorn-settings).
+
 ## Redis and Sidekiq
 
 Redis stores all user sessions and the background task queue.
@@ -172,6 +160,26 @@ default settings.
 If you would like to disable Prometheus and it's exporters or read more information
 about it, check the [Prometheus documentation](../administration/monitoring/prometheus/index.md).
 
+## GitLab Runner
+
+We strongly advise against installing GitLab Runner on the same machine you plan
+to install GitLab on. Depending on how you decide to configure GitLab Runner and
+what tools you use to exercise your application in the CI environment, GitLab
+Runner can consume significant amount of available memory.
+
+Memory consumption calculations, that are available above, will not be valid if
+you decide to run GitLab Runner and the GitLab Rails application on the same
+machine.
+
+It is also not safe to install everything on a single machine, because of the
+[security reasons] - especially when you plan to use shell executor with GitLab
+Runner.
+
+We recommend using a separate machine for each GitLab Runner, if you plan to
+use the CI features.
+
+[security reasons]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/blob/master/docs/security/index.md
+
 ## Supported web browsers
 
 We support the current and the previous major release of Firefox, Chrome/Chromium, Safari and Microsoft browsers (Microsoft Edge and Internet Explorer 11).
diff --git a/doc/integration/chat_commands.md b/doc/integration/chat_commands.md
index c878dc7e6500d9d1ef1f7b6d20a4bfe4895dfe40..2856992ee25a5aef7a8532b27b99ceb5c6e47ab2 100644
--- a/doc/integration/chat_commands.md
+++ b/doc/integration/chat_commands.md
@@ -1,14 +1 @@
-# Chat Commands
-
-Chat commands in Mattermost and Slack (also called Slack slash commands) allow you to control GitLab and view GitLab content right inside your chat client, without having to leave it. For Slack, this requires a [project service configuration](../user/project/integrations/slack_slash_commands.md). Simply type the command as a message in your chat client to activate it. 
-
-Commands are scoped to a project, with a trigger term that is specified during configuration. (We suggest you use the project name as the trigger term for simplicty and clarity.) Taking the trigger term as `project-name`, the commands are:
-
-
-| Command | Effect |
-| ------- | ------ |
-| `/project-name help` | Shows all available chat commands |
-| `/project-name issue new <title> <shift+return> <description>` | Creates a new issue with title `<title>` and description `<description>` |
-| `/project-name issue show <id>` | Shows the issue with id `<id>` |
-| `/project-name issue search <query>` | Shows up to 5 issues matching `<query>` |
-| `/project-name deploy <from> to <to>` | Deploy from the `<from>` environment to the `<to>` environment |
\ No newline at end of file
+This document was moved to [integration/slash_commands.md](slash_commands.md).
diff --git a/doc/integration/external-issue-tracker.md b/doc/integration/external-issue-tracker.md
index 265c891cf8357355d10a43f62469304ef685d285..2dd9b33273ce71cb14a4d232ec3a8f59a364b1c7 100644
--- a/doc/integration/external-issue-tracker.md
+++ b/doc/integration/external-issue-tracker.md
@@ -8,6 +8,9 @@ you to do the following:
   issue index of the external tracker
 - clicking **New issue** on the project dashboard creates a new issue on the
   external tracker
+- you can reference these external issues inside GitLab interface
+  (merge requests, commits, comments) and they will be automatically converted
+  into links
 
 ## Configuration
 
diff --git a/doc/integration/slash_commands.md b/doc/integration/slash_commands.md
new file mode 100644
index 0000000000000000000000000000000000000000..5d880ba785c46006db11cd15fc60204627121070
--- /dev/null
+++ b/doc/integration/slash_commands.md
@@ -0,0 +1,14 @@
+# Slash Commands
+
+Slash commands in Mattermost and Slack allow you to control GitLab and view GitLab content right inside your chat client, without having to leave it. For Slack, this requires a [project service configuration](../user/project/integrations/slack_slash_commands.md). Simply type the command as a message in your chat client to activate it.
+
+Commands are scoped to a project, with a trigger term that is specified during configuration. (We suggest you use the project name as the trigger term for simplicty and clarity.) Taking the trigger term as `project-name`, the commands are:
+
+
+| Command | Effect |
+| ------- | ------ |
+| `/project-name help` | Shows all available slash commands |
+| `/project-name issue new <title> <shift+return> <description>` | Creates a new issue with title `<title>` and description `<description>` |
+| `/project-name issue show <id>` | Shows the issue with id `<id>` |
+| `/project-name issue search <query>` | Shows up to 5 issues matching `<query>` |
+| `/project-name deploy <from> to <to>` | Deploy from the `<from>` environment to the `<to>` environment |
diff --git a/doc/update/8.9-to-8.10.md b/doc/update/8.9-to-8.10.md
index d6b2f11d49adf54c99a88f38f435af82914b3bca..42132f690d8c91a9914f779f5c4352ddc696133c 100644
--- a/doc/update/8.9-to-8.10.md
+++ b/doc/update/8.9-to-8.10.md
@@ -156,7 +156,7 @@ See [smtp_settings.rb.sample] as an example.
 Ensure you're still up-to-date with the latest init script changes:
 
     sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
-    
+
 For Ubuntu 16.04.1 LTS:
 
     sudo systemctl daemon-reload
diff --git a/doc/update/9.1-to-9.2.md b/doc/update/9.1-to-9.2.md
index e7d97fde14ebe0c627689f846f1c0d82a89655fa..225a4dcc924d5280d5d4a7c6bcafaf02ae17b072 100644
--- a/doc/update/9.1-to-9.2.md
+++ b/doc/update/9.1-to-9.2.md
@@ -70,7 +70,27 @@ curl --location https://yarnpkg.com/install.sh | bash -
 
 More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install).
 
-### 5. Get latest code
+### 5. Update Go
+
+NOTE: GitLab 9.2 and higher only supports Go 1.8.3 and dropped support for Go
+1.5.x through 1.7.x. Be sure to upgrade your installation if necessary.
+
+You can check which version you are running with `go version`.
+
+Download and install Go:
+
+```bash
+# Remove former Go installation folder
+sudo rm -rf /usr/local/go
+
+curl --remote-name --progress https://storage.googleapis.com/golang/go1.8.3.linux-amd64.tar.gz
+echo '1862f4c3d3907e59b04a757cfda0ea7aa9ef39274af99a784f5be843c80c6772  go1.8.3.linux-amd64.tar.gz' | shasum -a256 -c - && \
+  sudo tar -C /usr/local -xzf go1.8.3.linux-amd64.tar.gz
+sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/
+rm go1.8.3.linux-amd64.tar.gz
+```
+
+### 6. Get latest code
 
 ```bash
 cd /home/git/gitlab
@@ -97,7 +117,7 @@ cd /home/git/gitlab
 sudo -u git -H git checkout 9-2-stable-ee
 ```
 
-### 6. Update gitlab-shell
+### 7. Update gitlab-shell
 
 ```bash
 cd /home/git/gitlab-shell
@@ -107,11 +127,10 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_SHELL_VERSION)
 sudo -u git -H bin/compile
 ```
 
-### 7. Update gitlab-workhorse
+### 8. Update gitlab-workhorse
 
-Install and compile gitlab-workhorse. This requires
-[Go 1.8](https://golang.org/dl). Go (at least 1.5) should already be on your system from
-GitLab 8.1 and shall be upgraded if necessary. Please note that starting in Gitlab 9.3, only Go 1.8.3 and above will be supported. GitLab-Workhorse uses [GNU Make](https://www.gnu.org/software/make/).
+Install and compile gitlab-workhorse. GitLab-Workhorse uses
+[GNU Make](https://www.gnu.org/software/make/).
 If you are not using Linux you may have to run `gmake` instead of
 `make` below.
 
@@ -123,7 +142,7 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_WORKHORSE_VERSION)
 sudo -u git -H make
 ```
 
-### 8. Update configuration files
+### 9. Update configuration files
 
 #### New configuration options for `gitlab.yml`
 
@@ -197,7 +216,7 @@ For Ubuntu 16.04.1 LTS:
 sudo systemctl daemon-reload
 ```
 
-### 9. Install libs, migrations, etc.
+### 10. Install libs, migrations, etc.
 
 ```bash
 cd /home/git/gitlab
@@ -223,7 +242,7 @@ sudo -u git -H bundle exec rake 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).
 
-### 10. Optional: install Gitaly
+### 11. Optional: install Gitaly
 
 Gitaly is still an optional component of GitLab. If you want to save time
 during your 9.2 upgrade **you can skip this step**.
@@ -240,14 +259,14 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION)
 sudo -u git -H make
 ```
 
-### 11. Start application
+### 12. Start application
 
 ```bash
 sudo service gitlab start
 sudo service nginx restart
 ```
 
-### 12. Check application status
+### 13. Check application status
 
 Check if GitLab and its environment are configured correctly:
 
diff --git a/doc/update/9.2-to-9.3.md b/doc/update/9.2-to-9.3.md
index 0c32e4db53fd45954fbd9e8e764ea5177af9f3b2..097b996ec31fce6908bceafcf6e7550c1fad94a4 100644
--- a/doc/update/9.2-to-9.3.md
+++ b/doc/update/9.2-to-9.3.md
@@ -72,8 +72,8 @@ More information can be found on the [yarn website](https://yarnpkg.com/en/docs/
 
 ### 5. Update Go
 
-NOTE: GitLab 9.3 and higher only supports Go 1.8.3 and dropped support for Go 1.5.x through 1.7.x. Be
-sure to upgrade your installation if necessary
+NOTE: GitLab 9.2 and higher only supports Go 1.8.3 and dropped support for Go
+1.5.x through 1.7.x. Be sure to upgrade your installation if necessary.
 
 You can check which version you are running with `go version`.
 
@@ -117,7 +117,7 @@ cd /home/git/gitlab
 sudo -u git -H git checkout 9-3-stable-ee
 ```
 
-### 5. Update gitlab-shell
+### 7. Update gitlab-shell
 
 ```bash
 cd /home/git/gitlab-shell
@@ -127,11 +127,10 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_SHELL_VERSION)
 sudo -u git -H bin/compile
 ```
 
-### 6. Update gitlab-workhorse
+### 8. Update gitlab-workhorse
 
-Install and compile gitlab-workhorse. This requires
-[Go 1.5](https://golang.org/dl) which should already be on your system from
-GitLab 8.1. GitLab-Workhorse uses [GNU Make](https://www.gnu.org/software/make/).
+Install and compile gitlab-workhorse. GitLab-Workhorse uses
+[GNU Make](https://www.gnu.org/software/make/).
 If you are not using Linux you may have to run `gmake` instead of
 `make` below.
 
@@ -143,7 +142,7 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_WORKHORSE_VERSION)
 sudo -u git -H make
 ```
 
-### 7. Update Gitaly
+### 9. Update Gitaly
 
 If you have not yet set up Gitaly then follow [Gitaly section of the installation
 guide](../install/installation.md#install-gitaly).
@@ -157,7 +156,16 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION)
 sudo -u git -H make
 ```
 
-### 10. Update configuration files
+### 10. Update MySQL permissions
+
+If you are using MySQL you need to grant the GitLab user the necessary
+permissions on the database:
+
+```bash
+mysql -u root -p -e "GRANT TRIGGER ON \`gitlabhq_production\`.* TO 'git'@'localhost';"
+```
+
+### 11. Update configuration files
 
 #### New configuration options for `gitlab.yml`
 
@@ -231,7 +239,7 @@ For Ubuntu 16.04.1 LTS:
 sudo systemctl daemon-reload
 ```
 
-### 11. Install libs, migrations, etc.
+### 12. Install libs, migrations, etc.
 
 ```bash
 cd /home/git/gitlab
@@ -257,14 +265,14 @@ sudo -u git -H bundle exec rake 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).
 
-### 12. Start application
+### 13. Start application
 
 ```bash
 sudo service gitlab start
 sudo service nginx restart
 ```
 
-### 13. Check application status
+### 14. Check application status
 
 Check if GitLab and its environment are configured correctly:
 
diff --git a/doc/update/README.md b/doc/update/README.md
index d024a809f24b26101159a08652f3799556e028d2..22dbc7c750f6b6e4dacae83bf3b8a93900265a81 100644
--- a/doc/update/README.md
+++ b/doc/update/README.md
@@ -11,22 +11,6 @@ There are currently 3 official ways to install GitLab:
 
 Based on your installation, choose a section below that fits your needs.
 
----
-
-<!-- START doctoc generated TOC please keep comment here to allow auto update -->
-<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
-**Table of Contents**  *generated with [DocToc](https://github.com/thlorenz/doctoc)*
-
-- [Omnibus Packages](#omnibus-packages)
-- [Installation from source](#installation-from-source)
-- [Installation using Docker](#installation-using-docker)
-- [Upgrading between editions](#upgrading-between-editions)
-    - [Community to Enterprise Edition](#community-to-enterprise-edition)
-    - [Enterprise to Community Edition](#enterprise-to-community-edition)
-- [Miscellaneous](#miscellaneous)
-
-<!-- END doctoc generated TOC please keep comment here to allow auto update -->
-
 ## Omnibus Packages
 
 - The [Omnibus update guide](http://docs.gitlab.com/omnibus/update/README.html)
diff --git a/doc/user/discussions/index.md b/doc/user/discussions/index.md
index 59e343ebe51a4bdb784f55be12171f39dc28d01b..8b1d299484c18814693839f31c80f3b3ffb5ff0c 100644
--- a/doc/user/discussions/index.md
+++ b/doc/user/discussions/index.md
@@ -10,7 +10,7 @@ You can leave a comment in the following places:
 - commits
 - commit diffs
 
-The comment area supports [Markdown] and [slash commands]. One can edit their
+The comment area supports [Markdown] and [quick actions]. One can edit their
 own comment at any time, and anyone with [Master access level][permissions] or
 higher can also edit a comment made by someone else.
 
@@ -146,5 +146,5 @@ comments in greater detail.
 [discussion-view]: img/discussion_view.png
 [discussions-resolved]: img/discussions_resolved.png
 [markdown]: ../markdown.md
-[slash commands]: ../project/slash_commands.md
+[quick actions]: ../project/quick_actions.md
 [permissions]: ../permissions.md
diff --git a/doc/user/group/subgroups/index.md b/doc/user/group/subgroups/index.md
index c4921c74a17bcc9eb2c71ce87f406b0996104abc..5724dcfab480024784da3b610630a19c9f40d2aa 100644
--- a/doc/user/group/subgroups/index.md
+++ b/doc/user/group/subgroups/index.md
@@ -1,6 +1,9 @@
 # Subgroups
 
-> [Introduced][ce-2772] in GitLab 9.0.
+>**Notes:**
+- [Introduced][ce-2772] in GitLab 9.0.
+- Not available when using MySQL as external database (support removed in
+  GitLab 9.3 [due to performance reasons][issue]).
 
 With subgroups (aka nested groups or hierarchical groups) you can have
 up to 20 levels of nested groups, which among other things can help you to:
@@ -173,3 +176,4 @@ Here's a list of what you can't do with subgroups:
 [ce-2772]: https://gitlab.com/gitlab-org/gitlab-ce/issues/2772
 [permissions]: ../../permissions.md#group
 [reserved]:  https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/path_regex.rb
+[issue]: https://gitlab.com/gitlab-org/gitlab-ce/issues/30472#note_27747600
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index 3fda47b9e3436e53029f2f181b3eb1ac7d58c065..3d47e644ad2ad3f42d255fdb576781cd34325741 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -89,6 +89,7 @@ group.
 | Create project in group |       |          |           | ✓      | ✓     |
 | Manage group members    |       |          |           |        | ✓     |
 | Remove group            |       |          |           |        | ✓     |
+| Manage group labels     |       | ✓        | ✓         | ✓      | ✓     |
 
 ## External Users
 
diff --git a/doc/user/profile/personal_access_tokens.md b/doc/user/profile/personal_access_tokens.md
index 9488ce1ef300348d9ba775b8c596d94a5ac4f5be..f28c034e74c7041f4d59dd5d1fbc1363802369bc 100644
--- a/doc/user/profile/personal_access_tokens.md
+++ b/doc/user/profile/personal_access_tokens.md
@@ -14,6 +14,9 @@ accepted method of authentication when you have
 Once you have your token, [pass it to the API][usage] using either the
 `private_token` parameter or the `PRIVATE-TOKEN` header.
 
+The expiration of personal access tokens happens on the date you define,
+at midnight UTC.
+
 ## Creating a personal access token
 
 You can create as many personal access tokens as you like from your GitLab
diff --git a/doc/user/project/img/issue_board.png b/doc/user/project/img/issue_board.png
index b636cb294b8f15eb6bea243ab6d8ab75926a4536..cf7f519f783bff7fdd7ce00def10bd9b1417cb39 100644
Binary files a/doc/user/project/img/issue_board.png and b/doc/user/project/img/issue_board.png differ
diff --git a/doc/user/project/img/issue_board_add_list.png b/doc/user/project/img/issue_board_add_list.png
index cdfc466d23f2072888da452496026af56cf764f7..973d9f7cde4fe9e63147d20238294b053ee18b99 100644
Binary files a/doc/user/project/img/issue_board_add_list.png and b/doc/user/project/img/issue_board_add_list.png differ
diff --git a/doc/user/project/img/issue_board_move_issue_card_list.png b/doc/user/project/img/issue_board_move_issue_card_list.png
new file mode 100644
index 0000000000000000000000000000000000000000..c6b17ada40e8e1a373b62b714081bed2320952c8
Binary files /dev/null and b/doc/user/project/img/issue_board_move_issue_card_list.png differ
diff --git a/doc/user/project/img/issue_board_welcome_message.png b/doc/user/project/img/issue_board_welcome_message.png
index 5318e6ea4a9071175ed8c695be157856bc899ec2..127b9b08cc7d0ee2fddef63c13baa85aab38628d 100644
Binary files a/doc/user/project/img/issue_board_welcome_message.png and b/doc/user/project/img/issue_board_welcome_message.png differ
diff --git a/doc/user/project/img/issue_boards_add_issues_modal.png b/doc/user/project/img/issue_boards_add_issues_modal.png
index 33049dce74ffd65399b1d4c8fed4c38eb902fb81..bedaf724a15f729485b6cc84e0c390613c021468 100644
Binary files a/doc/user/project/img/issue_boards_add_issues_modal.png and b/doc/user/project/img/issue_boards_add_issues_modal.png differ
diff --git a/doc/user/project/integrations/bugzilla.md b/doc/user/project/integrations/bugzilla.md
index 0b219e8447834a1d704ee0a6012710d0c1063261..6a040516231a0de0500258ce13af2ba2f98e2e62 100644
--- a/doc/user/project/integrations/bugzilla.md
+++ b/doc/user/project/integrations/bugzilla.md
@@ -16,3 +16,14 @@ Once you have configured and enabled Bugzilla:
 - the **Issues** link on the GitLab project pages takes you to the appropriate
   Bugzilla product page
 - clicking **New issue** on the project dashboard takes you to Bugzilla for entering a new issue
+
+## Referencing issues in Bugzilla
+
+Issues in Bugzilla can be referenced in two alternative ways:
+1. `#<ID>` where `<ID>` is a number (example `#143`)
+2. `<PROJECT>-<ID>` where `<PROJECT>` starts with a capital letter which is
+  then followed by capital letters, numbers or underscores, and `<ID>` is
+  a number (example `API_32-143`).
+
+Please note that `<PROJECT>` part is ignored and links always point to the
+address specified in `issues_url`.
diff --git a/doc/user/project/integrations/img/merge_request_performance.png b/doc/user/project/integrations/img/merge_request_performance.png
index 93b2626fed7868a36cec5138cc4f5f245edaa331..eba6515a6ae01a9a9d33c9522fd7db083b5ed306 100644
Binary files a/doc/user/project/integrations/img/merge_request_performance.png and b/doc/user/project/integrations/img/merge_request_performance.png differ
diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md
index d3fb5916dc64263e74a69dcbcc77ec01291f0520..86ceb14b9653de2bec58a76d080ea3c6f4960e77 100644
--- a/doc/user/project/integrations/prometheus.md
+++ b/doc/user/project/integrations/prometheus.md
@@ -167,15 +167,15 @@ environment which has had a successful deployment.
 ## Determining the performance impact of a merge
 
 > [Introduced][ce-10408] in GitLab 9.2.
+> GitLab 9.3 added the [numeric comparison](https://gitlab.com/gitlab-org/gitlab-ce/issues/27439) of the 30 minute averages.
 
 Developers can view the performance impact of their changes within the merge
-request workflow. When a source branch has been deployed to an environment, a
-sparkline will appear showing the average memory consumption of the app. The dot
+request workflow. When a source branch has been deployed to an environment, a sparkline and numeric comparison of the average memory consumption will appear. On the sparkline, a dot
 indicates when the current changes were deployed, with up to 30 minutes of
-performance data displayed before and after. The sparkline will be updated after
+performance data displayed before and after. The comparison shows the difference between the 30 minute average before and after the deployment. This information is updated after
 each commit has been deployed.
 
-Once merged and the target branch has been redeployed, the sparkline will switch
+Once merged and the target branch has been redeployed, the metrics will switch
 to show the new environments this revision has been deployed to.
 
 Performance data will be available for the duration it is persisted on the
diff --git a/doc/user/project/integrations/redmine.md b/doc/user/project/integrations/redmine.md
index 89c0312d3c26260f642e3529217df702826edcab..8026f1f57bcf8da3f194fe9dd1e34193bc0e0ec1 100644
--- a/doc/user/project/integrations/redmine.md
+++ b/doc/user/project/integrations/redmine.md
@@ -21,3 +21,14 @@ Once you have configured and enabled Redmine:
 As an example, below is a configuration for a project named gitlab-ci.
 
 ![Redmine configuration](img/redmine_configuration.png)
+
+## Referencing issues in Redmine
+
+Issues in Redmine can be referenced in two alternative ways:
+1. `#<ID>` where `<ID>` is a number (example `#143`)
+2. `<PROJECT>-<ID>` where `<PROJECT>` starts with a capital letter which is
+  then followed by capital letters, numbers or underscores, and `<ID>` is
+  a number (example `API_32-143`).
+
+Please note that `<PROJECT>` part is ignored and links always point to the
+address specified in `issues_url`.
diff --git a/doc/user/project/integrations/slack_slash_commands.md b/doc/user/project/integrations/slack_slash_commands.md
index 54e0ee611cb0fd2f4df19413ecd46af521c8aeb9..c267da69bb3707db76ef58d5991665cf795e9b81 100644
--- a/doc/user/project/integrations/slack_slash_commands.md
+++ b/doc/user/project/integrations/slack_slash_commands.md
@@ -2,7 +2,7 @@
 
 > Introduced in GitLab 8.15
 
-Slack slash commands (also known as chat commmands) allow you to control GitLab and view content right inside Slack, without having to leave it. This requires configurations in both Slack and GitLab. 
+Slack slash commands allow you to control GitLab and view content right inside Slack, without having to leave it. This requires configurations in both Slack and GitLab.
 
 > Note: GitLab can also send events (e.g. issue created) to Slack as notifications. This is the separately configured [Slack Notifications Service](slack.md).
 
@@ -20,4 +20,4 @@ Slack slash commands (also known as chat commmands) allow you to control GitLab
 
 ## Usage
 
-You can now use the [Slack slash commands](../../../integration/chat_commands.md).
\ No newline at end of file
+You can now use the [Slack slash commands](../../../integration/slash_commands.md).
diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md
index 0517ed3ec1863e628813a8e7064ef7465c0bdee9..023c6932e41ef4ad4bd5060a1577ddaa7cfaa8b8 100644
--- a/doc/user/project/integrations/webhooks.md
+++ b/doc/user/project/integrations/webhooks.md
@@ -736,7 +736,7 @@ X-Gitlab-Event: Merge Request Hook
 
 ### Wiki Page events
 
-Triggered when a wiki page is created, edited or deleted.
+Triggered when a wiki page is created, updated or deleted.
 
 **Request Header**:
 
diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md
index ebea7062ecb6c384a0e672d24f5fcaef5eaa59e7..e2cc67726e04f222fb109574a5691958c0f86230 100644
--- a/doc/user/project/issue_board.md
+++ b/doc/user/project/issue_board.md
@@ -1,8 +1,7 @@
-# Issue board
+# Issue Board
 
->**Notes:**
-- [Introduced][ce-5554] in GitLab 8.11.
-- The Backlog column was replaced by the **Add issues** button in GitLab 8.17.
+>**Note:**
+[Introduced][ce-5554] in [GitLab 8.11](https://about.gitlab.com/2016/08/22/gitlab-8-11-released/#issue-board).
 
 The GitLab Issue Board is a software project management tool used to plan,
 organize, and visualize a workflow for a feature or product release.
@@ -15,12 +14,65 @@ Other interesting links:
 
 ## Overview
 
-The Issue Board builds on GitLab's existing issue tracking functionality and
+The Issue Board builds on GitLab's existing
+[issue tracking functionality](issues/index.md#issue-tracker) and
 leverages the power of [labels] by utilizing them as lists of the scrum board.
 
-With the Issue Board you can have a different view of your issues while also
+With the Issue Board you can have a different view of your issues while
 maintaining the same filtering and sorting abilities you see across the
-issue tracker.
+issue tracker. An Issue Board is based on its project's label structure, therefore, it
+applies the same descriptive labels to indicate placement on the board, keeping
+consistency throughout the entire development lifecycle.
+
+An Issue Board shows you what issues your team is working on, who is assigned to each,
+and where in the workflow those issues are.
+
+You create issues, host code, perform reviews, build, test,
+and deploy from one single platform. Issue Boards help you to visualize
+and manage the entire process _in_ GitLab.
+
+With [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards), available
+only in [GitLab Enterprise Edition](https://about.gitlab.com/gitlab-ee/),
+you go even further, as you can not only keep yourself and your project
+organized from a broader perspective with one Issue Board per project,
+but also allow your team members to organize their own workflow by creating
+multiple Issue Boards within the same project.
+
+## Use cases
+
+GitLab Workflow allows you to discuss proposals in issues, categorize them
+with labels, and from there organize and prioritize them with Issue Boards.
+
+For example, let's consider this simplified development workflow:
+
+1. You have a repository hosting your app's codebase
+and your team actively contributing to code
+1. Your **backend** team starts working a new
+implementation, gathers feedback and approval, and pass it over to **frontend**
+1. When frontend is complete, the new feature is deployed to **staging** to be tested
+1. When successful, it is deployed to **production**
+
+If we have the labels "**backend**", "**frontend**", "**staging**", and
+"**production**", and an Issue Board with a list for each, we can:
+
+- Visualize the entire flow of implementations since the
+beginning of the development lifecycle until deployed to production
+- Prioritize the issues in a list by moving them vertically
+- Move issues between lists to organize them according to the labels you've set
+- Add multiple issues to lists in the board by selecting one or more existing issues
+
+![issue card moving](img/issue_board_move_issue_card_list.png)
+
+> **Notes:**
+>
+>- For a broader use case, please check the blog post
+[GitLab Workflow, an Overview](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/#gitlab-workflow-use-case-scenario).
+>
+>- For a real use case, please check why
+[Codepen decided to adopt Issue Boards](https://about.gitlab.com/2017/01/27/codepen-welcome-to-gitlab/#project-management-everything-in-one-place)
+to improve their workflow with [multiple boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards).
+
+## Issue Board terminology
 
 Below is a table of the definitions used for GitLab's Issue Board.
 
@@ -57,7 +109,7 @@ In short, here's a list of actions you can take in an Issue Board:
 If you are not able to perform one or more of the things above, make sure you
 have the right [permissions](#permissions).
 
-## First time using the issue board
+## First time using the Issue Board
 
 The first time you navigate to your Issue Board, you will be presented with
 a default list (**Done**) and a welcoming message that gives
@@ -98,7 +150,7 @@ list view that is removed. You can always add it back later if you need.
 ## Adding issues to a list
 
 You can add issues to a list by clicking the **Add issues** button that is
-present in the upper right corner of the issue board. This will open up a modal
+present in the upper right corner of the Issue Board. This will open up a modal
 window where you can see all the issues that do not belong to any list.
 
 Select one or more issues by clicking on the cards and then click **Add issues**
diff --git a/doc/user/project/issues/confidential_issues.md b/doc/user/project/issues/confidential_issues.md
index 208be7d0ed579bd47a8cc7684e7faa66a31c5072..1760b1821142fb1b034bc63e63757bd2b7157e25 100644
--- a/doc/user/project/issues/confidential_issues.md
+++ b/doc/user/project/issues/confidential_issues.md
@@ -43,8 +43,9 @@ next to the issues that are marked as confidential.
 
 ---
 
-While inside the issue, you can see a persistent dark banner at the top of the
-screen.
+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)
 
diff --git a/doc/user/project/issues/img/confidential_issues_issue_page.png b/doc/user/project/issues/img/confidential_issues_issue_page.png
index 91f7cc8d3caabe9d07b4d6f57b2f5bd14c8b28f3..f04ec8ff32bf49b31873f678c37933ecad1ef516 100755
Binary files a/doc/user/project/issues/img/confidential_issues_issue_page.png and b/doc/user/project/issues/img/confidential_issues_issue_page.png differ
diff --git a/doc/user/project/issues/index.md b/doc/user/project/issues/index.md
index fe87e6f9495ebcda7cb24f9aa180cb45bf17506b..e55e2aea023282500c540c11cd3e1aa9c2dc0496 100644
--- a/doc/user/project/issues/index.md
+++ b/doc/user/project/issues/index.md
@@ -1,4 +1,4 @@
-# Issues documentation
+# Issues
 
 The GitLab Issue Tracker is an advanced and complete tool
 for tracking the evolution of a new idea or the process
diff --git a/doc/user/project/issues/issues_functionalities.md b/doc/user/project/issues/issues_functionalities.md
index ba843201e1a9c6f89b8b0574dceea0c430ed8832..294176e61f92b5394f2f8fe5e120f4fa228589cf 100644
--- a/doc/user/project/issues/issues_functionalities.md
+++ b/doc/user/project/issues/issues_functionalities.md
@@ -68,7 +68,7 @@ This feature is available only in [GitLab Enterprise Edition](https://about.gitl
 - Spend: add the time spent on the implementation of that issue
 
 > **Note:**
-both estimate and spend times are set via [GitLab Slash Commands](../slash_commands.md).
+both estimate and spend times are set via [GitLab Quick Actions](../quick_actions.md).
 
 Learn more on the [Time Tracking documentation](https://docs.gitlab.com/ee/workflow/time_tracking.html).
 
@@ -147,7 +147,7 @@ or in the issue thread.
 
 #### 15. Award emoji
 
-- Award an emoji to that issue. 
+- Award an emoji to that issue.
 
 > **Tip:**
 Posting "+1" as comments in threads spam all
diff --git a/doc/user/project/pipelines/settings.md b/doc/user/project/pipelines/settings.md
index 1d2eba4f74bbc0479a6037720c65dd133074f851..3ff5a08d72c56b5097382dd48ecad32aa1926829 100644
--- a/doc/user/project/pipelines/settings.md
+++ b/doc/user/project/pipelines/settings.md
@@ -1,7 +1,7 @@
 # Pipelines settings
 
 To reach the pipelines settings navigate to your project's
-**Settings ➔ CI/CD Pipelines**.
+**Settings ➔ Pipelines**.
 
 The following settings can be configured per project.
 
@@ -27,6 +27,22 @@ The default value is 60 minutes. Decrease the time limit if you want to impose
 a hard limit on your jobs' running time or increase it otherwise. In any case,
 if the job surpasses the threshold, it is marked as failed.
 
+## Custom CI config path
+
+>  - [Introduced][ce-12509] in GitLab 9.4.
+
+By default we look for the `.gitlab-ci.yml` file in the project's root
+directory. If you require a different location **within** the repository,
+you can set a custom filepath that will be used to lookup the config file,
+this filepath should be **relative** to the root.
+
+Here are some valid examples:
+
+> * .gitlab-ci.yml
+> * .my-custom-file.yml
+> * my/path/.gitlab-ci.yml
+> * my/path/.my-custom-file.yml
+
 ## Test coverage parsing
 
 If you use test coverage in your code, GitLab can capture its output in the
@@ -59,8 +75,8 @@ pipelines** checkbox and save the changes.
 
 > [Introduced][ce-9362] in GitLab 9.1.
 
-If you want to auto-cancel all pending non-HEAD pipelines on branch, when 
-new pipeline will be created (after your git push or manually from UI), 
+If you want to auto-cancel all pending non-HEAD pipelines on branch, when
+new pipeline will be created (after your git push or manually from UI),
 check **Auto-cancel pending pipelines** checkbox and save the changes.
 
 ## Badges
@@ -115,3 +131,4 @@ into your `README.md`:
 [var]: ../../../ci/yaml/README.md#git-strategy
 [coverage report]: #test-coverage-parsing
 [ce-9362]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9362
+[ce-12509]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12509
diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md
new file mode 100644
index 0000000000000000000000000000000000000000..19b51c83222df56a9f3fcf12ada2ef5ddef799e4
--- /dev/null
+++ b/doc/user/project/quick_actions.md
@@ -0,0 +1,39 @@
+# GitLab quick actions
+
+Quick actions are textual shortcuts for common actions on issues or merge
+requests that are usually done by clicking buttons or dropdowns in GitLab's UI.
+You can enter these commands while creating a new issue or merge request, and
+in comments. Each command should be on a separate line in order to be properly
+detected and executed. The commands are removed from the issue, merge request or
+comment body before it is saved and will not be visible to anyone else.
+
+Below is a list of all of the available commands and descriptions about what they
+do.
+
+| Command                    | Action       |
+|:---------------------------|:-------------|
+| `/close`                   | Close the issue or merge request |
+| `/reopen`                  | Reopen the issue or merge request |
+| `/merge`                   | Merge (when pipeline succeeds) |
+| `/title <New title>`       | Change title |
+| `/assign @username`        | Assign |
+| `/unassign`                | Remove assignee |
+| `/milestone %milestone`    | Set milestone |
+| `/remove_milestone`        | Remove milestone |
+| `/label ~foo ~"bar baz"`   | Add label(s) |
+| `/unlabel ~foo ~"bar baz"` | Remove all or specific label(s) |
+| `/relabel ~foo ~"bar baz"` | Replace all label(s) |
+| `/todo`                    | Add a todo |
+| `/done`                    | Mark todo as done |
+| `/subscribe`               | Subscribe |
+| `/unsubscribe`             | Unsubscribe |
+| <code>/due &lt;in 2 days &#124; this Friday &#124; December 31st&gt;</code> | Set due date |
+| `/remove_due_date`         | Remove due date |
+| `/wip`                     | Toggle the Work In Progress status |
+| <code>/estimate &lt;1w 3d 2h 14m&gt;</code> | Set time estimate |
+| `/remove_estimate`       | Remove estimated time |
+| <code>/spend &lt;1h 30m &#124; -1h 5m&gt;</code> | Add or subtract spent time |
+| `/remove_time_spent`       | Remove time spent |
+| `/target_branch <Branch Name>` | Set target branch for current merge request |
+| `/award :emoji:`  | Toggle award for :emoji: |
+| `/board_move ~column`      | Move issue to column on the board |
diff --git a/doc/user/project/repository/branches/img/delete_merged_branches.png b/doc/user/project/repository/branches/img/delete_merged_branches.png
new file mode 100644
index 0000000000000000000000000000000000000000..1856a624f74cef6f3711175a1381a7dd99c106ce
Binary files /dev/null and b/doc/user/project/repository/branches/img/delete_merged_branches.png differ
diff --git a/doc/user/project/repository/branches/index.md b/doc/user/project/repository/branches/index.md
new file mode 100644
index 0000000000000000000000000000000000000000..1948627ee79d5b28902d064e17d17cc25fb1fc5d
--- /dev/null
+++ b/doc/user/project/repository/branches/index.md
@@ -0,0 +1,17 @@
+# Branches
+
+## Delete merged branches
+
+> [Introduced][ce-6449] in GitLab 8.14.
+
+![Delete merged branches](img/delete_merged_branches.png)
+
+This feature allows merged branches to be deleted in bulk. Only branches that
+have been merged and [are not protected][protected] will be deleted as part of
+this operation.
+
+It's particularly useful to clean up old branches that were not deleting
+automatically when a merge request was merged.
+
+[ce-6449]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6449 "Add button to delete all merged branches"
+[protected]: ../../protected_branches.md
diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md
index 58d2fd76c6102d3c6d1edb0b578dab244c0cd514..35960ade3d4550d6b44708b1e0a8105cf029be6d 100644
--- a/doc/user/project/settings/import_export.md
+++ b/doc/user/project/settings/import_export.md
@@ -27,14 +27,15 @@ with all their related data and be moved into a new GitLab instance.
 
 | GitLab version | Import/Export version |
 | -------- | -------- |
-| 9.2.0 to current | 0.1.7 |
-| 8.17.0   | 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    |
-| 8.9.5    | 0.1.1    |
-| 8.9.0    | 0.1.0    |
+| 9.4.0 to current | 0.1.8 |
+| 9.2.0    | 0.1.7 |
+| 8.17.0   | 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 |
+| 8.9.5    | 0.1.1 |
+| 8.9.0    | 0.1.0 |
 
  > The table reflects what GitLab version we updated the Import/Export version at.
  > For instance, 8.10.3 and 8.11 will have the same Import/Export version (0.1.3)
diff --git a/doc/user/project/slash_commands.md b/doc/user/project/slash_commands.md
index 08452ca75cd3d7a88b9aa5905fd5e35fc37ab8ab..e9103a3f49cc76dc218a7a63617d58f5d75b97f3 100644
--- a/doc/user/project/slash_commands.md
+++ b/doc/user/project/slash_commands.md
@@ -1,39 +1 @@
-# GitLab slash commands
-
-Slash commands are textual shortcuts for common actions on issues or merge
-requests that are usually done by clicking buttons or dropdowns in GitLab's UI.
-You can enter these commands while creating a new issue or merge request, and
-in comments. Each command should be on a separate line in order to be properly
-detected and executed. The commands are removed from the issue, merge request or
-comment body before it is saved and will not be visible to anyone else.
-
-Below is a list of all of the available commands and descriptions about what they
-do.
-
-| Command                    | Action       |
-|:---------------------------|:-------------|
-| `/close`                   | Close the issue or merge request |
-| `/reopen`                  | Reopen the issue or merge request |
-| `/merge`                   | Merge (when pipeline succeeds) |
-| `/title <New title>`       | Change title |
-| `/assign @username`        | Assign |
-| `/unassign`                | Remove assignee |
-| `/milestone %milestone`    | Set milestone |
-| `/remove_milestone`        | Remove milestone |
-| `/label ~foo ~"bar baz"`   | Add label(s) |
-| `/unlabel ~foo ~"bar baz"` | Remove all or specific label(s) |
-| `/relabel ~foo ~"bar baz"` | Replace all label(s) |
-| `/todo`                    | Add a todo |
-| `/done`                    | Mark todo as done |
-| `/subscribe`               | Subscribe |
-| `/unsubscribe`             | Unsubscribe |
-| <code>/due &lt;in 2 days &#124; this Friday &#124; December 31st&gt;</code> | Set due date |
-| `/remove_due_date`         | Remove due date |
-| `/wip`                     | Toggle the Work In Progress status |
-| <code>/estimate &lt;1w 3d 2h 14m&gt;</code> | Set time estimate |
-| `/remove_estimate`       | Remove estimated time |
-| <code>/spend &lt;1h 30m &#124; -1h 5m&gt;</code> | Add or subtract spent time |
-| `/remove_time_spent`       | Remove time spent |
-| `/target_branch <Branch Name>` | Set target branch for current merge request |
-| `/award :emoji:`  | Toggle award for :emoji: |
-| `/board_move ~column`      | Move issue to column on the board |
+This document was moved to [user/project/quick_actions.md](quick_actions.md).
diff --git a/doc/workflow/README.md b/doc/workflow/README.md
index 604c7d5cefb046d13e58e2df866a39ad2362a016..54d4028a50aadf431f2070cbba25ed0515829780 100644
--- a/doc/workflow/README.md
+++ b/doc/workflow/README.md
@@ -21,7 +21,7 @@
 - [Project users](add-user/add-user.md)
 - [Protected branches](../user/project/protected_branches.md)
 - [Protected tags](../user/project/protected_tags.md)
-- [Slash commands](../user/project/slash_commands.md)
+- [Quick Actions](../user/project/quick_actions.md)
 - [Sharing a project with a group](share_with_group.md)
 - [Share projects with other groups](share_projects_with_other_groups.md)
 - [Time tracking](time_tracking.md)
diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md
index e10ccc4fc46d84deb31378d8e480353c54519e39..ea28968fbb2fa4dfac6404c69109483db818f8f8 100644
--- a/doc/workflow/gitlab_flow.md
+++ b/doc/workflow/gitlab_flow.md
@@ -300,7 +300,7 @@ If there are no merge conflicts and the feature branches are short lived the ris
 If there are merge conflicts you merge the master branch into the feature branch and the CI server will rerun the tests.
 If you have long lived feature branches that last for more than a few days you should make your issues smaller.
 
-## Working wih feature branches
+## Working with feature branches
 
 ![Shell output showing git pull output](git_pull.png)
 
diff --git a/doc/workflow/time_tracking.md b/doc/workflow/time_tracking.md
index de12994c516e6bafa7e523341c38e8215beedb41..bfe87bb2ceb02efb6fc3bfe90c83a1cc41c91849 100644
--- a/doc/workflow/time_tracking.md
+++ b/doc/workflow/time_tracking.md
@@ -21,13 +21,13 @@ below.
 
 ## How to enter data
 
-Time Tracking uses two [slash commands] that GitLab introduced with this new
+Time Tracking uses two [quick actions] that GitLab introduced with this new
 feature: `/spend` and `/estimate`.
 
-Slash commands can be used in the body of an issue or a merge request, but also
+Quick actions can be used in the body of an issue or a merge request, but also
 in a comment in both an issue or a merge request.
 
-Below is an example of how you can use those new slash commands inside a comment.
+Below is an example of how you can use those new quick actions inside a comment.
 
 ![Time tracking example in a comment](time-tracking/time-tracking-example.png)
 
@@ -70,4 +70,4 @@ The following time units are available:
 Default conversion rates are 1w = 5d and 1d = 8h.
 
 [landing]: https://about.gitlab.com/features/time-tracking
-[slash-commands]: ../user/project/slash_commands.md
+[quick actions]: ../user/project/quick_actions.md
diff --git a/features/dashboard/merge_requests.feature b/features/dashboard/merge_requests.feature
deleted file mode 100644
index 4a2c997d707f7dfae6877a3bec2fd94e2eb0df96..0000000000000000000000000000000000000000
--- a/features/dashboard/merge_requests.feature
+++ /dev/null
@@ -1,21 +0,0 @@
-@dashboard
-Feature: Dashboard Merge Requests
-  Background:
-    Given I sign in as a user
-    And I have authored merge requests
-    And I have assigned merge requests
-    And I have other merge requests
-    And I visit dashboard merge requests page
-
-  Scenario: I should see assigned merge_requests
-    Then I should see merge requests assigned to me
-
-  @javascript
-  Scenario: I should see authored merge_requests
-    When I click "Authored by me" link
-    Then I should see merge requests authored by me
-
-  @javascript
-  Scenario: I should see all merge_requests
-    When I click "All" link
-    Then I should see all merge requests
diff --git a/features/dashboard/new_project.feature b/features/dashboard/new_project.feature
deleted file mode 100644
index 046e2815d4ea8d3735c1f5c94c712d21ea701454..0000000000000000000000000000000000000000
--- a/features/dashboard/new_project.feature
+++ /dev/null
@@ -1,30 +0,0 @@
-@dashboard
-Feature: New Project
-Background:
-  Given I sign in as a user
-  And I own project "Shop"
-  And I visit dashboard page
-  And I click "New project" link
-
-  @javascript
-  Scenario: I should see New Projects page
-  Then I see "New Project" page
-  Then I see all possible import options
-
-  @javascript
-  Scenario: I should see instructions on how to import from Git URL
-  Given I see "New Project" page
-  When I click on "Repo by URL"
-  Then I see instructions on how to import from Git URL
-
-  @javascript
-  Scenario: I should see instructions on how to import from GitHub
-  Given I see "New Project" page
-  When I click on "Import project from GitHub"
-  Then I am redirected to the GitHub import page
-
-  @javascript
-  Scenario: I should see Google Code import page
-  Given I see "New Project" page
-  When I click on "Google Code"
-  Then I redirected to Google Code import page
diff --git a/features/dashboard/todos.feature b/features/dashboard/todos.feature
deleted file mode 100644
index 0b23bbb7951d822562206cff658b863b30c0b10a..0000000000000000000000000000000000000000
--- a/features/dashboard/todos.feature
+++ /dev/null
@@ -1,28 +0,0 @@
-@dashboard
-Feature: Dashboard Todos
-  Background:
-    Given I sign in as a user
-    And I own project "Shop"
-    And "John Doe" is a developer of project "Shop"
-    And "Mary Jane" is a developer of project "Shop"
-    And "Mary Jane" owns private project "Enterprise"
-    And I am a developer of project "Enterprise"
-    And I have todos
-    And I visit dashboard todos page
-
-  @javascript
-  Scenario: I mark todos as done
-    Then I should see todos assigned to me
-    And I mark the todo as done
-    Then I should see the todo marked as done
-
-  @javascript
-  Scenario: I mark all todos as done
-    Then I should see todos assigned to me
-    And I mark all todos as done
-    Then I should see all todos marked as done
-
-  @javascript
-    Scenario: I click on a todo row
-      Given I click on the todo
-      Then I should be directed to the corresponding page
diff --git a/features/group/members.feature b/features/group/members.feature
index e539f6a127300ffac1db1f31de7ed79af5c80d03..49a44f57cbb7c3df9e633ed24fd9e13b5376c29b 100644
--- a/features/group/members.feature
+++ b/features/group/members.feature
@@ -4,65 +4,6 @@ Feature: Group Members
     And "John Doe" is owner of group "Owned"
     And "John Doe" is guest of group "Guest"
 
-  # Leave
-
-  @javascript
-  Scenario: Owner should be able to remove himself from group if he is not the last owner
-    Given "Mary Jane" is owner of group "Owned"
-    When I visit group "Owned" members page
-    Then I should see user "John Doe" in team list
-    Then I should see user "Mary Jane" in team list
-    When I click on the "Remove User From Group" button for "John Doe"
-    And I visit group "Owned" members page
-    Then I should not see user "John Doe" in team list
-    Then I should see user "Mary Jane" in team list
-
-  @javascript
-  Scenario: Owner should not be able to remove himself from group if he is the last owner
-    Given "Mary Jane" is guest of group "Owned"
-    When I visit group "Owned" members page
-    Then I should see user "John Doe" in team list
-    Then I should see user "Mary Jane" in team list
-    Then I should not see the "Remove User From Group" button for "John Doe"
-
-  @javascript
-  Scenario: Guest should be able to remove himself from group
-    Given "Mary Jane" is guest of group "Guest"
-    When I visit group "Guest" members page
-    Then I should see user "John Doe" in team list
-    Then I should see user "Mary Jane" in team list
-    When I click on the "Remove User From Group" button for "John Doe"
-    When I visit group "Guest" members page
-    Then I should not see user "John Doe" in team list
-    Then I should see user "Mary Jane" in team list
-
-  @javascript
-  Scenario: Guest should be able to remove himself from group even if he is the only user in the group
-    When I visit group "Guest" members page
-    Then I should see user "John Doe" in team list
-    When I click on the "Remove User From Group" button for "John Doe"
-    When I visit group "Guest" members page
-    Then I should not see user "John Doe" in team list
-
-  # Remove others
-
-  Scenario: Owner should be able to remove other users from group
-    Given "Mary Jane" is owner of group "Owned"
-    When I visit group "Owned" members page
-    Then I should see user "John Doe" in team list
-    Then I should see user "Mary Jane" in team list
-    When I click on the "Remove User From Group" button for "Mary Jane"
-    When I visit group "Owned" members page
-    Then I should see user "John Doe" in team list
-    Then I should not see user "Mary Jane" in team list
-
-  Scenario: Guest should not be able to remove other users from group
-    Given "Mary Jane" is guest of group "Guest"
-    When I visit group "Guest" members page
-    Then I should see user "John Doe" in team list
-    Then I should see user "Mary Jane" in team list
-    Then I should not see the "Remove User From Group" button for "Mary Jane"
-
   Scenario: Search member by name
     Given "Mary Jane" is guest of group "Guest"
     And I visit group "Guest" members page
diff --git a/features/profile/notifications.feature b/features/profile/notifications.feature
deleted file mode 100644
index ef8743932f510ab05eeaa0ba7654b7160cedc6c8..0000000000000000000000000000000000000000
--- a/features/profile/notifications.feature
+++ /dev/null
@@ -1,15 +0,0 @@
-@profile
-Feature: Profile Notifications
-  Background:
-    Given I sign in as a user
-    And I own project "Shop"
-
-  Scenario: I visit notifications tab
-    When I visit profile notifications page
-    Then I should see global notifications settings
-
-  @javascript
-  Scenario: I edit Project Notifications
-    Given I visit profile notifications page
-    When I select Mention setting from dropdown
-    Then I should see Notification saved message
diff --git a/features/project/create.feature b/features/project/create.feature
deleted file mode 100644
index 67336d73bf729a94699cd4ccdf6de0857815ca28..0000000000000000000000000000000000000000
--- a/features/project/create.feature
+++ /dev/null
@@ -1,14 +0,0 @@
-@project-create
-Feature: Project Create
-  In order to get access to project sections
-  A user with ability to create a project
-  Should be able to create a new one
-
-  @javascript
-  Scenario: User create a project
-    Given I sign in as a user
-    And I have an ssh key
-    When I visit new project page
-    And fill project form with valid data
-    Then I should see project page
-    And I should see empty project instructions
diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature
index 472ec9544f3024952977f83298d0df33c33f61fc..59a625056d6a3572814bd8c4988525d87d25661d 100644
--- a/features/project/source/browse_files.feature
+++ b/features/project/source/browse_files.feature
@@ -130,16 +130,6 @@ Feature: Project Source Browse Files
     When I click on "Changes" tab
     Then I can see the new text file
 
-  @javascript
-  Scenario: If I enter an illegal file name I see an error message
-    Given I click on "New file" link in repo
-    And I fill the new file name with an illegal name
-    And I edit code
-    And I fill the commit message
-    And I click on "Commit changes"
-    Then I am on the new file page
-    And I see "Path can contain only..."
-
   @javascript
   Scenario: I can create file with a directory name
     Given I click on "New file" link in repo
diff --git a/features/snippets/snippets.feature b/features/snippets/snippets.feature
deleted file mode 100644
index 1ad027802299fed1b4cd146a72f51f972ee2344e..0000000000000000000000000000000000000000
--- a/features/snippets/snippets.feature
+++ /dev/null
@@ -1,40 +0,0 @@
-@snippets
-Feature: Snippets
-  Background:
-    Given I sign in as a user
-    And I have public "Personal snippet one" snippet
-    And I have private "Personal snippet private" snippet
-
-  @javascript
-  Scenario: I create new snippet
-    Given I visit new snippet page
-    And I submit new snippet "Personal snippet three"
-    Then I should see snippet "Personal snippet three"
-
-  Scenario: I update "Personal snippet one"
-    Given I visit snippet page "Personal snippet one"
-    And I click link "Edit"
-    And I submit new title "Personal snippet new title"
-    Then I should see "Personal snippet new title"
-
-  Scenario: Set "Personal snippet one" public
-    Given I visit snippet page "Personal snippet one"
-    And I click link "Edit"
-    And I uncheck "Private" checkbox
-    Then I should see "Personal snippet one" public
-
-  Scenario: I destroy "Personal snippet one"
-    Given I visit snippet page "Personal snippet one"
-    And I click link "Delete"
-    Then I should not see "Personal snippet one" in snippets
-
-  Scenario: I create new internal snippet
-    Given I logout directly
-    And I sign in as an admin
-    Then I visit new snippet page
-    And I submit new internal snippet
-    Then I visit snippet page "Internal personal snippet one"
-    And I logout directly
-    Then I sign in as a user
-    Given I visit new snippet page
-    Then I visit snippet page "Internal personal snippet one"
diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb
index 71c69a4fdea79cd4b3ce1a4a8390ab0b2a8e8c70..0960f49aad3e76a728c59d931799652839d58966 100644
--- a/features/steps/dashboard/dashboard.rb
+++ b/features/steps/dashboard/dashboard.rb
@@ -27,7 +27,7 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps
 
   step 'I see prefilled new Merge Request page' do
     expect(page).to have_selector('.merge-request-form')
-    expect(current_path).to eq new_namespace_project_merge_request_path(@project.namespace, @project)
+    expect(current_path).to eq project_new_merge_request_path(@project)
     expect(find("#merge_request_target_project_id").value).to eq @project.id.to_s
     expect(find("input#merge_request_source_branch").value).to eq "fix"
     expect(find("input#merge_request_target_branch").value).to eq "master"
diff --git a/features/steps/dashboard/merge_requests.rb b/features/steps/dashboard/merge_requests.rb
deleted file mode 100644
index 909ffec3646fad1c7957c22e4be874f48136b919..0000000000000000000000000000000000000000
--- a/features/steps/dashboard/merge_requests.rb
+++ /dev/null
@@ -1,121 +0,0 @@
-class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
-  include SharedAuthentication
-  include SharedPaths
-  include Select2Helper
-
-  step 'I should see merge requests assigned to me' do
-    should_see(assigned_merge_request)
-    should_see(assigned_merge_request_from_fork)
-    should_not_see(authored_merge_request)
-    should_not_see(authored_merge_request_from_fork)
-    should_not_see(other_merge_request)
-  end
-
-  step 'I should see merge requests authored by me' do
-    should_see(authored_merge_request)
-    should_see(authored_merge_request_from_fork)
-    should_not_see(assigned_merge_request)
-    should_not_see(assigned_merge_request_from_fork)
-    should_not_see(other_merge_request)
-  end
-
-  step 'I should see all merge requests' do
-    should_see(authored_merge_request)
-    should_see(assigned_merge_request)
-    should_see(other_merge_request)
-  end
-
-  step 'I have authored merge requests' do
-    authored_merge_request
-    authored_merge_request_from_fork
-  end
-
-  step 'I have assigned merge requests' do
-    assigned_merge_request
-    assigned_merge_request_from_fork
-  end
-
-  step 'I have other merge requests' do
-    other_merge_request
-  end
-
-  step 'I click "Authored by me" link' do
-    find("#assignee_id").set("")
-    find(".js-author-search", match: :first).click
-    find(".dropdown-menu-author li a", match: :first, text: current_user.to_reference).click
-  end
-
-  step 'I click "All" link' do
-    find(".js-author-search").click
-    expect(page).to have_selector(".dropdown-menu-author li a")
-    find(".dropdown-menu-author li a", match: :first).click
-    expect(page).not_to have_selector(".dropdown-menu-author li a")
-
-    find(".js-assignee-search").click
-    expect(page).to have_selector(".dropdown-menu-assignee li a")
-    find(".dropdown-menu-assignee li a", match: :first).click
-    expect(page).not_to have_selector(".dropdown-menu-assignee li a")
-  end
-
-  def should_see(merge_request)
-    expect(page).to have_content(merge_request.title[0..10])
-  end
-
-  def should_not_see(merge_request)
-    expect(page).not_to have_content(merge_request.title[0..10])
-  end
-
-  def assigned_merge_request
-    @assigned_merge_request ||= create :merge_request,
-                                  assignee: current_user,
-                                  target_project: project,
-                                  source_project: project
-  end
-
-  def authored_merge_request
-    @authored_merge_request ||= create :merge_request,
-                                  source_branch: 'markdown',
-                                  author: current_user,
-                                  target_project: project,
-                                  source_project: project
-  end
-
-  def other_merge_request
-    @other_merge_request ||= create :merge_request,
-                              source_branch: 'fix',
-                              target_project: project,
-                              source_project: project
-  end
-
-  def authored_merge_request_from_fork
-    @authored_merge_request_from_fork ||= create :merge_request,
-                                            source_branch: 'feature_conflict',
-                                            author: current_user,
-                                            target_project: public_project,
-                                            source_project: forked_project
-  end
-
-  def assigned_merge_request_from_fork
-    @assigned_merge_request_from_fork ||= create :merge_request,
-                                            source_branch: 'markdown',
-                                            assignee: current_user,
-                                            target_project: public_project,
-                                            source_project: forked_project
-  end
-
-  def project
-    @project ||= begin
-                   project = create(:project, :repository)
-                   project.team << [current_user, :master]
-                   project
-                 end
-  end
-
-  def public_project
-    @public_project ||= create(:project, :public, :repository)
-  end
-
-  def forked_project
-    @forked_project ||= Projects::ForkService.new(public_project, current_user).execute
-  end
-end
diff --git a/features/steps/dashboard/new_project.rb b/features/steps/dashboard/new_project.rb
deleted file mode 100644
index 530fd6f7bdb8148995dc20dcb3824b54dcd51c60..0000000000000000000000000000000000000000
--- a/features/steps/dashboard/new_project.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-class Spinach::Features::NewProject < Spinach::FeatureSteps
-  include SharedAuthentication
-  include SharedPaths
-  include SharedProject
-
-  step 'I click "New project" link' do
-    page.within '#content-body' do
-      click_link "New project"
-    end
-  end
-
-  step 'I click "New project" in top right menu' do
-    page.within '.header-content' do
-      click_link "New project"
-    end
-  end
-
-  step 'I see "New Project" page' do
-    expect(page).to have_content('Project path')
-    expect(page).to have_content('Project name')
-  end
-
-  step 'I see all possible import options' do
-    expect(page).to have_link('GitHub')
-    expect(page).to have_link('Bitbucket')
-    expect(page).to have_link('GitLab.com')
-    expect(page).to have_link('Google Code')
-    expect(page).to have_button('Repo by URL')
-    expect(page).to have_link('GitLab export')
-  end
-
-  step 'I click on "Import project from GitHub"' do
-    first('.import_github').click
-  end
-
-  step 'I am redirected to the GitHub import page' do
-    expect(page).to have_content('Import Projects from GitHub')
-    expect(current_path).to eq new_import_github_path
-  end
-
-  step 'I click on "Repo by URL"' do
-    first('.import_git').click
-  end
-
-  step 'I see instructions on how to import from Git URL' do
-    git_import_instructions = first('.js-toggle-content')
-    expect(git_import_instructions).to be_visible
-    expect(git_import_instructions).to have_content "Git repository URL"
-  end
-
-  step 'I click on "Google Code"' do
-    first('.import_google_code').click
-  end
-
-  step 'I redirected to Google Code import page' do
-    expect(page).to have_content('Import projects from Google Code')
-    expect(current_path).to eq new_import_google_code_path
-  end
-end
diff --git a/features/steps/dashboard/starred_projects.rb b/features/steps/dashboard/starred_projects.rb
deleted file mode 100644
index c33813e550b8e7c3f5b51cf202f46e1a5694ce17..0000000000000000000000000000000000000000
--- a/features/steps/dashboard/starred_projects.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-class Spinach::Features::DashboardStarredProjects < Spinach::FeatureSteps
-  include SharedAuthentication
-  include SharedPaths
-  include SharedProject
-
-  step 'I starred project "Community"' do
-    current_user.toggle_star(Project.find_by(name: 'Community'))
-  end
-
-  step 'I should not see project "Shop"' do
-    page.within '.projects-list' do
-      expect(page).not_to have_content('Shop')
-    end
-  end
-end
diff --git a/features/steps/dashboard/todos.rb b/features/steps/dashboard/todos.rb
deleted file mode 100644
index 4a33babe3bd2fa37c0cd012d3cf5e5f0bd9e4bc0..0000000000000000000000000000000000000000
--- a/features/steps/dashboard/todos.rb
+++ /dev/null
@@ -1,191 +0,0 @@
-class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
-  include SharedAuthentication
-  include SharedPaths
-  include SharedProject
-  include SharedUser
-  include WaitForRequests
-
-  step '"John Doe" is a developer of project "Shop"' do
-    project.team << [john_doe, :developer]
-  end
-
-  step 'I am a developer of project "Enterprise"' do
-    enterprise.team << [current_user, :developer]
-  end
-
-  step '"Mary Jane" is a developer of project "Shop"' do
-    project.team << [john_doe, :developer]
-  end
-
-  step 'I have todos' do
-    create(:todo, user: current_user, project: project, author: mary_jane, target: issue, action: Todo::MENTIONED)
-    create(:todo, user: current_user, project: project, author: john_doe, target: issue, action: Todo::ASSIGNED)
-    note = create(:note, author: john_doe, noteable: issue, note: "#{current_user.to_reference} Wdyt?", project: project)
-    create(:todo, user: current_user, project: project, author: john_doe, target: issue, action: Todo::MENTIONED, note: note)
-    create(:todo, user: current_user, project: project, author: john_doe, target: merge_request, action: Todo::ASSIGNED)
-  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-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_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
-    page.within('.todo:nth-child(1)') do
-      click_link 'Done'
-    end
-
-    page.within('.todos-count') { expect(page).to have_content '3' }
-    expect(page).to have_content 'To do 3'
-    expect(page).to have_content 'Done 1'
-    should_see_todo(1, "John Doe assigned you merge request #{merge_request.to_reference(full: true)}", merge_request.title, state: :done_reversible)
-  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)
-
-    find('.js-todos-mark-all').trigger('click')
-
-    page.within('.todos-count') { expect(page).to have_content '0' }
-    expect(page).to have_content 'To do 0'
-    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_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
-    find('.todos-done a').trigger('click')
-
-    expect(page).to have_link project.name_with_namespace
-    should_see_todo(1, "John Doe assigned you merge request #{merge_request.to_reference(full: true)}", merge_request.title, state: :done_irreversible)
-  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)
-
-    find('.todos-done a').trigger('click')
-
-    expect(page).to have_link project.name_with_namespace
-    should_see_todo(1, "John Doe assigned you merge request #{merge_request_reference}", merge_request.title, state: :done_irreversible)
-    should_see_todo(2, "John Doe mentioned you on issue #{issue_reference}", "#{current_user.to_reference} Wdyt?", state: :done_irreversible)
-    should_see_todo(3, "John Doe assigned you issue #{issue_reference}", issue.title, state: :done_irreversible)
-    should_see_todo(4, "Mary Jane mentioned you on issue #{issue_reference}", issue.title, state: :done_irreversible)
-  end
-
-  step 'I filter by "Enterprise"' do
-    click_button 'Project'
-    page.within '.dropdown-menu-project' do
-      click_link enterprise.name_with_namespace
-    end
-  end
-
-  step 'I filter by "John Doe"' do
-    click_button 'Author'
-    page.within '.dropdown-menu-author' do
-      click_link john_doe.username
-    end
-  end
-
-  step 'I filter by "Issue"' do
-    click_button 'Type'
-    page.within '.dropdown-menu-type' do
-      click_link 'Issue'
-    end
-  end
-
-  step 'I filter by "Mentioned"' do
-    click_button 'Action'
-    page.within '.dropdown-menu-action' do
-      click_link 'Mentioned'
-    end
-  end
-
-  step 'I should not see todos' do
-    expect(page).to have_content "You're all done!"
-  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(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(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(full: true)}"
-    should_not_see_todo "John Doe assigned you issue #{issue.to_reference(full: true)}"
-  end
-
-  step 'I click on the todo' do
-    find('.todo:nth-child(1)').click
-  end
-
-  step 'I should be directed to the corresponding page' do
-    page.should have_css('.identifier', text: 'Merge request !1')
-    # Merge request page loads and issues a number of Ajax requests
-    wait_for_requests
-  end
-
-  def should_see_todo(position, title, body, state: :pending)
-    page.within(".todo:nth-child(#{position})") do
-      expect(page).to have_content title
-      expect(page).to have_content body
-
-      if state == :pending
-        expect(page).to have_link 'Done'
-      elsif state == :done_reversible
-        expect(page).to have_link 'Undo'
-      elsif state == :done_irreversible
-        expect(page).not_to have_link 'Undo'
-        expect(page).not_to have_link 'Done'
-      else
-        raise 'Invalid state given, valid states: :pending, :done_reversible, :done_irreversible'
-      end
-    end
-  end
-
-  def should_not_see_todo(title)
-    expect(page).not_to have_visible_content title
-  end
-
-  def have_visible_content(text)
-    have_css('*', text: text, visible: true)
-  end
-
-  def john_doe
-    @john_doe ||= user_exists("John Doe", { username: "john_doe" })
-  end
-
-  def mary_jane
-    @mary_jane ||= user_exists("Mary Jane", { username: "mary_jane" })
-  end
-
-  def enterprise
-    @enterprise ||= Project.find_by(name: 'Enterprise')
-  end
-
-  def issue
-    @issue ||= create(:issue, assignees: [current_user], project: project)
-  end
-
-  def merge_request
-    @merge_request ||= create(:merge_request, assignee: current_user, source_project: project)
-  end
-end
diff --git a/features/steps/explore/projects.rb b/features/steps/explore/projects.rb
index 1a55f40abb903f6a0b77ddfc461464b8d0518b97..f1288c15084329743c96aaaa1dcff451567fbb74 100644
--- a/features/steps/explore/projects.rb
+++ b/features/steps/explore/projects.rb
@@ -66,7 +66,7 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
        title: "New feature",
        project: public_project
           )
-    visit namespace_project_issues_path(public_project.namespace, public_project)
+    visit project_issues_path(public_project)
   end
 
   step 'I should see list of issues for "Community" project' do
@@ -84,7 +84,7 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
        title: "New internal feature",
        project: internal_project
           )
-    visit namespace_project_issues_path(internal_project.namespace, internal_project)
+    visit project_issues_path(internal_project)
   end
 
   step 'I should see list of issues for "Internal" project' do
@@ -94,7 +94,7 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
   end
 
   step 'I visit "Community" merge requests page' do
-    visit namespace_project_merge_requests_path(public_project.namespace, public_project)
+    visit project_merge_requests_path(public_project)
   end
 
   step 'project "Community" has "Bug fix" open merge request' do
@@ -111,7 +111,7 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
   end
 
   step 'I visit "Internal" merge requests page' do
-    visit namespace_project_merge_requests_path(internal_project.namespace, internal_project)
+    visit project_merge_requests_path(internal_project)
   end
 
   step 'project "Internal" has "Feature implemented" open merge request' do
diff --git a/features/steps/group/milestones.rb b/features/steps/group/milestones.rb
index 0542b06c0ab0a9a47fdae268cfb26eea7f9a37ce..7288dc87005464ce34cb89f82ee83318c9d528aa 100644
--- a/features/steps/group/milestones.rb
+++ b/features/steps/group/milestones.rb
@@ -39,7 +39,7 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
     expect(page).to have_content('Milestone GL-113')
     expect(page).to have_content('Issues 3 Open: 3 Closed: 0')
     issue = Milestone.find_by(name: 'GL-113').issues.first
-    expect(page).to have_link(issue.title, href: namespace_project_issue_path(issue.project.namespace, issue.project, issue))
+    expect(page).to have_link(issue.title, href: project_issue_path(issue.project, issue))
   end
 
   step 'I fill milestone name' do
diff --git a/features/steps/groups.rb b/features/steps/groups.rb
index 25bb374b868eaf1dc7c634fb914cd8a0402a280b..0aedc422563ef74b6be57042304323b17af5a971 100644
--- a/features/steps/groups.rb
+++ b/features/steps/groups.rb
@@ -5,7 +5,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
   include SharedUser
 
   step 'I should see group "Owned"' do
-    expect(page).to have_content '@owned'
+    expect(page).to have_content 'Owned'
   end
 
   step 'I am a signed out user' do
diff --git a/features/steps/project/archived.rb b/features/steps/project/archived.rb
index b6f1d417e21a620540a7b19f101e0e375300c8aa..e4847180be9617fbde807d158452e434311ff864 100644
--- a/features/steps/project/archived.rb
+++ b/features/steps/project/archived.rb
@@ -15,7 +15,7 @@ class Spinach::Features::ProjectArchived < Spinach::FeatureSteps
 
   When 'I visit project "Forum" page' do
     project = Project.find_by(name: "Forum")
-    visit namespace_project_path(project.namespace, project)
+    visit project_path(project)
   end
 
   step 'I should not see "Archived"' do
diff --git a/features/steps/project/badges/build.rb b/features/steps/project/badges/build.rb
index 96c59322f9b2f59d7a0231a836c2d5bb751f996b..5a9094ee9d3544df8c2640bf68d2c5149551ea4c 100644
--- a/features/steps/project/badges/build.rb
+++ b/features/steps/project/badges/build.rb
@@ -5,7 +5,7 @@ class Spinach::Features::ProjectBadgesBuild < Spinach::FeatureSteps
   include RepoHelpers
 
   step 'I display builds badge for a master branch' do
-    visit build_namespace_project_badges_path(@project.namespace, @project, ref: :master, format: :svg)
+    visit build_project_badges_path(@project, ref: :master, format: :svg)
   end
 
   step 'I should see a build success badge' do
diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb
index f19fa1c7600a8803ad25e191d41099c55f20d0e6..305fff37c418a3ade5376e38ffcad3da79deef9a 100644
--- a/features/steps/project/commits/commits.rb
+++ b/features/steps/project/commits/commits.rb
@@ -33,7 +33,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
   end
 
   step 'I click on commit link' do
-    visit namespace_project_commit_path(@project.namespace, @project, sample_commit.id)
+    visit project_commit_path(@project, sample_commit.id)
   end
 
   step 'I see commit info' do
@@ -73,7 +73,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
   end
 
   step 'I visit commits list page for feature branch' do
-    visit namespace_project_commits_path(@project.namespace, @project, 'feature', { limit: 5 })
+    visit project_commits_path(@project, 'feature', { limit: 5 })
   end
 
   step 'I see feature branch commits' do
@@ -119,7 +119,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
 
   step 'I should see button to the merge request' do
     merge_request = MergeRequest.find_by(title: 'Feature')
-    expect(page).to have_link "View open merge request", href: namespace_project_merge_request_path(@project.namespace, @project, merge_request)
+    expect(page).to have_link "View open merge request", href: project_merge_request_path(@project, merge_request)
   end
 
   step 'I see breadcrumb links' do
@@ -135,7 +135,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
   end
 
   step 'I visit a commit with an image that changed' do
-    visit namespace_project_commit_path(@project.namespace, @project, sample_image_commit.id)
+    visit project_commit_path(@project, sample_image_commit.id)
   end
 
   step 'The diff links to both the previous and current image' do
diff --git a/features/steps/project/commits/revert.rb b/features/steps/project/commits/revert.rb
index 114de129d19d1522c6138722a3dd8ae4411ed57d..ebfa7a878bb066681b9cc52d2aee488e7d5b12d1 100644
--- a/features/steps/project/commits/revert.rb
+++ b/features/steps/project/commits/revert.rb
@@ -6,7 +6,7 @@ class Spinach::Features::RevertCommits < Spinach::FeatureSteps
   include RepoHelpers
 
   step 'I click on commit link' do
-    visit namespace_project_commit_path(@project.namespace, @project, sample_commit.id)
+    visit project_commit_path(@project, sample_commit.id)
   end
 
   step 'I click on the revert button' do
diff --git a/features/steps/project/commits/user_lookup.rb b/features/steps/project/commits/user_lookup.rb
index 2d43be5a38623d76483cecc4bbcb1f81aa060a0b..4599e0d032a81cd92f470bd2ee0f70444e368706 100644
--- a/features/steps/project/commits/user_lookup.rb
+++ b/features/steps/project/commits/user_lookup.rb
@@ -4,11 +4,11 @@ class Spinach::Features::ProjectCommitsUserLookup < Spinach::FeatureSteps
   include SharedPaths
 
   step 'I click on commit link' do
-    visit namespace_project_commit_path(@project.namespace, @project, sample_commit.id)
+    visit project_commit_path(@project, sample_commit.id)
   end
 
   step 'I click on another commit link' do
-    visit namespace_project_commit_path(@project.namespace, @project, sample_commit.parent_id)
+    visit project_commit_path(@project, sample_commit.parent_id)
   end
 
   step 'I have user with primary email' do
diff --git a/features/steps/project/create.rb b/features/steps/project/create.rb
index 28be9c6df5bc1ce519e7ee292f352e35ef532f98..60fa232672e86a2812457d3e4ef3a0767b5f27f0 100644
--- a/features/steps/project/create.rb
+++ b/features/steps/project/create.rb
@@ -7,12 +7,12 @@ class Spinach::Features::ProjectCreate < Spinach::FeatureSteps
     fill_in 'project_path', with: 'Empty'
     page.within '#content-body' do
       click_button "Create project"
-    end  
+    end
   end
 
   step 'I should see project page' do
     expect(page).to have_content "Empty"
-    expect(current_path).to eq namespace_project_path(Project.last.namespace, Project.last)
+    expect(current_path).to eq project_path(Project.last)
   end
 
   step 'I should see empty project instructions' do
diff --git a/features/steps/project/deploy_keys.rb b/features/steps/project/deploy_keys.rb
index 8ad9d4a47419c164370b41ee43bdb7b5dd4b542d..b58d595c7c6eb1780bac3f571a0206c5c603e609 100644
--- a/features/steps/project/deploy_keys.rb
+++ b/features/steps/project/deploy_keys.rb
@@ -36,7 +36,7 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps
   end
 
   step 'I should be on deploy keys page' do
-    expect(current_path).to eq namespace_project_settings_repository_path(@project.namespace, @project)
+    expect(current_path).to eq project_settings_repository_path(@project)
   end
 
   step 'I should see newly created deploy key' do
diff --git a/features/steps/project/fork.rb b/features/steps/project/fork.rb
index 35df403a85f214c7a562ffb2667b48a8314d91b8..dd4dff7f7a993f317847329e208aa42b76bbdc51 100644
--- a/features/steps/project/fork.rb
+++ b/features/steps/project/fork.rb
@@ -53,7 +53,7 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps
 
   step 'I visit the forks page of the "Shop" project' do
     @project = Project.where(name: 'Shop').last
-    visit namespace_project_forks_path(@project.namespace, @project)
+    visit project_forks_path(@project)
   end
 
   step 'I should see my fork on the list' do
diff --git a/features/steps/project/forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb
index 2d9d3efd9d407d995716dfe4b6d6223217ab6ab7..c6cabace25b7e10955e40cd00ea3f3a7c352115e 100644
--- a/features/steps/project/forked_merge_requests.rb
+++ b/features/steps/project/forked_merge_requests.rb
@@ -25,7 +25,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
   step 'I should see merge request "Merge Request On Forked Project"' do
     expect(@project.merge_requests.size).to be >= 1
     @merge_request = @project.merge_requests.last
-    expect(current_path).to eq namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
+    expect(current_path).to eq project_merge_request_path(@project, @merge_request)
     expect(@merge_request.title).to eq "Merge Request On Forked Project"
     expect(@merge_request.source_project).to eq @forked_project
     expect(@merge_request.source_branch).to eq "fix"
@@ -77,7 +77,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
     expect(page).to have_content "An Edited Forked Merge Request"
     expect(@project.merge_requests.size).to be >= 1
     @merge_request = @project.merge_requests.last
-    expect(current_path).to eq namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
+    expect(current_path).to eq project_merge_request_path(@project, @merge_request)
     expect(@merge_request.source_project).to eq @forked_project
     expect(@merge_request.source_branch).to eq "fix"
     expect(@merge_request.target_branch).to eq "master"
@@ -97,7 +97,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
   end
 
   step 'I see the edit page prefilled for "Merge Request On Forked Project"' do
-    expect(current_path).to eq edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
+    expect(current_path).to eq edit_project_merge_request_path(@project, @merge_request)
     expect(page).to have_content "Edit merge request #{@merge_request.to_reference}"
     expect(find("#merge_request_title").value).to eq "Merge Request On Forked Project"
   end
diff --git a/features/steps/project/graph.rb b/features/steps/project/graph.rb
index 176d04d721cc430a92d279b108780ada5630f995..e78e25318a6d852ed060e5e7a3a215f74666f0e7 100644
--- a/features/steps/project/graph.rb
+++ b/features/steps/project/graph.rb
@@ -7,19 +7,19 @@ class Spinach::Features::ProjectGraph < Spinach::FeatureSteps
   end
 
   When 'I visit project "Shop" graph page' do
-    visit namespace_project_graph_path(project.namespace, project, "master")
+    visit project_graph_path(project, "master")
   end
 
   step 'I visit project "Shop" commits graph page' do
-    visit commits_namespace_project_graph_path(project.namespace, project, "master")
+    visit commits_project_graph_path(project, "master")
   end
 
   step 'I visit project "Shop" languages graph page' do
-    visit languages_namespace_project_graph_path(project.namespace, project, "master")
+    visit languages_project_graph_path(project, "master")
   end
 
   step 'I visit project "Shop" chart page' do
-    visit charts_namespace_project_graph_path(project.namespace, project, "master")
+    visit charts_project_graph_path(project, "master")
   end
 
   step 'page should have languages graphs' do
@@ -33,7 +33,7 @@ class Spinach::Features::ProjectGraph < Spinach::FeatureSteps
   end
 
   step 'I visit project "Shop" CI graph page' do
-    visit ci_namespace_project_graph_path(project.namespace, project, 'master')
+    visit ci_project_graph_path(project, 'master')
   end
 
   step 'page should have CI graphs' do
diff --git a/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb
index 2324edda975c60c0987b7bdac1021fc09f07f13a..bbd284b4633069b174d25095048542a29c863004 100644
--- a/features/steps/project/issues/award_emoji.rb
+++ b/features/steps/project/issues/award_emoji.rb
@@ -5,7 +5,7 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
   include Select2Helper
 
   step 'I visit "Bugfix" issue page' do
-    visit namespace_project_issue_path(@project.namespace, @project, @issue)
+    visit project_issue_path(@project, @issue)
   end
 
   step 'I click the thumbsup award Emoji' do
diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb
index e4a559d8ff5e2ceb7fcaf54d8d5cfef0eb1c2722..2deef9036d36f8e33bcc73dae9f9791ee0799393 100644
--- a/features/steps/project/issues/issues.rb
+++ b/features/steps/project/issues/issues.rb
@@ -247,7 +247,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
 
   When 'I visit empty project page' do
     project = Project.find_by(name: 'Empty Project')
-    visit namespace_project_path(project.namespace, project)
+    visit project_path(project)
   end
 
   step 'I see empty project details with ssh clone info' do
@@ -259,12 +259,12 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
 
   When "I visit project \"Community\" issues page" do
     project = Project.find_by(name: 'Community')
-    visit namespace_project_issues_path(project.namespace, project)
+    visit project_issues_path(project)
   end
 
   When "I visit empty project's issues page" do
     project = Project.find_by(name: 'Empty Project')
-    visit namespace_project_issues_path(project.namespace, project)
+    visit project_issues_path(project)
   end
 
   step 'I leave a comment with code block' do
diff --git a/features/steps/project/issues/labels.rb b/features/steps/project/issues/labels.rb
index 2828e41f731c9680b06e3dd0a74ad10160a8f318..dac18c537ac5cf692f0515e3b92f0b5cdcbc4598 100644
--- a/features/steps/project/issues/labels.rb
+++ b/features/steps/project/issues/labels.rb
@@ -4,7 +4,7 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
   include SharedPaths
 
   step 'I visit \'bug\' label edit page' do
-    visit edit_namespace_project_label_path(project.namespace, project, bug_label)
+    visit edit_project_label_path(project, bug_label)
   end
 
   step 'I remove label \'bug\'' do
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index 69f5d0f8410319d516ad1aa1f0f62b0021cb4428..810cd75591b98961bf63d2564c850ba419b85096 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -65,7 +65,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
   end
 
   step 'I should not see "master" branch' do
-    expect(find('.merge-request-info')).not_to have_content "master"
+    expect(find('.issuable-info')).not_to have_content "master"
   end
 
   step 'I should see "feature_conflict" branch' do
@@ -256,7 +256,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
   end
 
   step 'I switch to the merge request\'s comments tab' do
-    visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+    visit project_merge_request_path(project, merge_request)
   end
 
   step 'I click on the commit in the merge request' do
diff --git a/features/steps/project/merge_requests/acceptance.rb b/features/steps/project/merge_requests/acceptance.rb
index 870dc862992a9fc5bf8dccac24619bd1309fe385..3c640e3512a1611465740fca213ea7fa736ae8b3 100644
--- a/features/steps/project/merge_requests/acceptance.rb
+++ b/features/steps/project/merge_requests/acceptance.rb
@@ -1,6 +1,5 @@
 class Spinach::Features::ProjectMergeRequestsAcceptance < Spinach::FeatureSteps
   include LoginHelpers
-  include GitlabRoutingHelper
   include WaitForRequests
 
   step 'I am on the Merge Request detail page' do
diff --git a/features/steps/project/merge_requests/revert.rb b/features/steps/project/merge_requests/revert.rb
index 98d990f112f9d6e19a96d3c62b1083755f0cbbfd..25ccf5ab180f15cb32624dc79d835d3e6d86df44 100644
--- a/features/steps/project/merge_requests/revert.rb
+++ b/features/steps/project/merge_requests/revert.rb
@@ -1,6 +1,5 @@
 class Spinach::Features::RevertMergeRequests < Spinach::FeatureSteps
   include LoginHelpers
-  include GitlabRoutingHelper
   include WaitForRequests
 
   step 'I click on the revert button' do
diff --git a/features/steps/project/network_graph.rb b/features/steps/project/network_graph.rb
index 370e46265c7b0d121d2cc155bc8576b2807074dd..ba98d861e7bf8b4e630b5a11f15a21857d9a088b 100644
--- a/features/steps/project/network_graph.rb
+++ b/features/steps/project/network_graph.rb
@@ -12,11 +12,11 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps
     Network::Graph.stub(max_count: 10)
 
     @project = Project.find_by(name: "Shop")
-    visit namespace_project_network_path(@project.namespace, @project, "master")
+    visit project_network_path(@project, "master")
   end
 
   step "I visit project network page on branch 'test'" do
-    visit namespace_project_network_path(@project.namespace, @project, "'test'")
+    visit project_network_path(@project, "'test'")
   end
 
   step 'page should select "master" in select box' do
diff --git a/features/steps/project/pages.rb b/features/steps/project/pages.rb
index 4e6830f738b4cd765d2523c72f761be84b91c399..275fb4fc010ce16dfc3c97df9da708c27c9c32f4 100644
--- a/features/steps/project/pages.rb
+++ b/features/steps/project/pages.rb
@@ -15,7 +15,7 @@ class Spinach::Features::ProjectPages < Spinach::FeatureSteps
   end
 
   step 'I visit the Project Pages' do
-    visit namespace_project_pages_path(@project.namespace, @project)
+    visit project_pages_path(@project)
   end
 
   step 'I should see the usage of GitLab Pages' do
@@ -75,7 +75,7 @@ class Spinach::Features::ProjectPages < Spinach::FeatureSteps
   end
 
   step 'I visit add a new Pages Domain' do
-    visit new_namespace_project_pages_domain_path(@project.namespace, @project)
+    visit new_project_pages_domain_path(@project)
   end
 
   step 'I fill the domain' do
diff --git a/features/steps/project/project_group_links.rb b/features/steps/project/project_group_links.rb
index 5280a38ce818c8de61654a0c67bec4cef8f49c18..47ee31786a69e714b1ba46f8eec8eee5dfdcc93e 100644
--- a/features/steps/project/project_group_links.rb
+++ b/features/steps/project/project_group_links.rb
@@ -42,7 +42,7 @@ class Spinach::Features::ProjectGroupLinks < Spinach::FeatureSteps
   end
 
   step 'I visit project group links page' do
-    visit namespace_project_group_links_path(project.namespace, project)
+    visit project_group_links_path(project)
   end
 
   def project
diff --git a/features/steps/project/redirects.rb b/features/steps/project/redirects.rb
index 92936f27c2054ec9b56801698494876d6c872530..b2ceb8dd9a8e4887ab6b0f40a7f851098ad60b4c 100644
--- a/features/steps/project/redirects.rb
+++ b/features/steps/project/redirects.rb
@@ -13,7 +13,7 @@ class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps
 
   step 'I visit project "Community" page' do
     project = Project.find_by(name: 'Community')
-    visit namespace_project_path(project.namespace, project)
+    visit project_path(project)
   end
 
   step 'I should see project "Community" home page' do
@@ -25,12 +25,12 @@ class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps
 
   step 'I visit project "Enterprise" page' do
     project = Project.find_by(name: 'Enterprise')
-    visit namespace_project_path(project.namespace, project)
+    visit project_path(project)
   end
 
   step 'I visit project "CommunityDoesNotExist" page' do
     project = Project.find_by(name: 'Community')
-    visit namespace_project_path(project.namespace, project) + 'DoesNotExist'
+    visit project_path(project) + 'DoesNotExist'
   end
 
   step 'I click on "Sign In"' do
diff --git a/features/steps/project/services.rb b/features/steps/project/services.rb
index 6bac4df16f896add453b89029445ebeceeb6114b..906a81b29b34de179c2e4f2dae89e799398403d1 100644
--- a/features/steps/project/services.rb
+++ b/features/steps/project/services.rb
@@ -4,7 +4,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
   include SharedPaths
 
   step 'I visit project "Shop" services page' do
-    visit namespace_project_settings_integrations_path(@project.namespace, @project)
+    visit project_settings_integrations_path(@project)
   end
 
   step 'I should see list of available services' do
diff --git a/features/steps/project/snippets.rb b/features/steps/project/snippets.rb
index dd49701a3d98a455a69b1ef51dfbfb77969a241d..b0407d3f07d2913f6c268fc0eb13880c9fd6e64c 100644
--- a/features/steps/project/snippets.rb
+++ b/features/steps/project/snippets.rb
@@ -91,7 +91,7 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps
   end
 
   step 'I visit snippet page "Snippet one"' do
-    visit namespace_project_snippet_path(project.namespace, project, project_snippet)
+    visit project_snippet_path(project, project_snippet)
   end
 
   def project_snippet
diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb
index 80aa3a047a02553afae0908c1a3c0f01b6ae7e53..621cae5d80d3098306556f14a7cf489cf1065e0e 100644
--- a/features/steps/project/source/browse_files.rb
+++ b/features/steps/project/source/browse_files.rb
@@ -9,7 +9,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
   step "I don't have write access" do
     @project = create(:project, :repository, name: "Other Project", path: "other-project")
     @project.team << [@user, :reporter]
-    visit namespace_project_tree_path(@project.namespace, @project, root_ref)
+    visit project_tree_path(@project, root_ref)
   end
 
   step 'I should see files from repository' do
@@ -19,7 +19,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
   end
 
   step 'I should see files from repository for "6d39438"' do
-    expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, "6d39438")
+    expect(current_path).to eq project_tree_path(@project, "6d39438")
     expect(page).to have_content ".gitignore"
     expect(page).to have_content "LICENSE"
   end
@@ -92,10 +92,6 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
     fill_in :branch_name, with: 'new_branch_name', visible: true
   end
 
-  step 'I fill the new file name with an illegal name' do
-    fill_in :file_name, with: 'Spaces Not Allowed'
-  end
-
   step 'I fill the new file name with a new directory' do
     fill_in :file_name, with: new_file_name_with_directory
   end
@@ -240,16 +236,16 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
   end
 
   step 'I am redirected to the files URL' do
-    expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, 'master')
+    expect(current_path).to eq project_tree_path(@project, 'master')
   end
 
   step 'I am redirected to the ".gitignore"' do
-    expect(current_path).to eq(namespace_project_blob_path(@project.namespace, @project, 'master/.gitignore'))
+    expect(current_path).to eq(project_blob_path(@project, 'master/.gitignore'))
   end
 
   step 'I am redirected to the permalink URL' do
     expect(current_path).to(
-      eq(namespace_project_blob_path(@project.namespace, @project,
+      eq(project_blob_path(@project,
                                      @project.repository.commit.sha +
                                      '/.gitignore'))
     )
@@ -257,26 +253,26 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
 
   step 'I am redirected to the new file' do
     expect(current_path).to eq(
-      namespace_project_blob_path(@project.namespace, @project, 'master/' + new_file_name))
+      project_blob_path(@project, 'master/' + new_file_name))
   end
 
   step 'I am redirected to the new file with directory' do
     expect(current_path).to eq(
-      namespace_project_blob_path(@project.namespace, @project, 'master/' + new_file_name_with_directory))
+      project_blob_path(@project, 'master/' + new_file_name_with_directory))
   end
 
   step 'I am redirected to the new merge request page' do
-    expect(current_path).to eq(new_namespace_project_merge_request_path(@project.namespace, @project))
+    expect(current_path).to eq(project_new_merge_request_path(@project))
   end
 
   step "I am redirected to the fork's new merge request page" do
     fork = @user.fork_of(@project)
-    expect(current_path).to eq(new_namespace_project_merge_request_path(fork.namespace, fork))
+    expect(current_path).to eq(project_new_merge_request_path(fork))
   end
 
   step 'I am redirected to the root directory' do
     expect(current_path).to eq(
-      namespace_project_tree_path(@project.namespace, @project, 'master'))
+      project_tree_path(@project, 'master'))
   end
 
   step "I don't see the permalink link" do
@@ -327,11 +323,11 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
   end
 
   step "I visit the 'test' tree" do
-    visit namespace_project_tree_path(@project.namespace, @project, "'test'")
+    visit project_tree_path(@project, "'test'")
   end
 
   step "I visit the fix tree" do
-    visit namespace_project_tree_path(@project.namespace, @project, "fix/.testdir")
+    visit project_tree_path(@project, "fix/.testdir")
   end
 
   step 'I see the commit data' do
@@ -346,7 +342,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
 
   step 'I click on "files/lfs/lfs_object.iso" file in repo' do
     allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(true)
-    visit namespace_project_tree_path(@project.namespace, @project, "lfs")
+    visit project_tree_path(@project, "lfs")
     click_link 'files'
     click_link "lfs"
     click_link "lfs_object.iso"
@@ -369,7 +365,6 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
       expect(page).to have_content 'Permalink'
       expect(page).not_to have_content 'Edit'
       expect(page).not_to have_content 'Blame'
-      expect(page).not_to have_content 'Annotate'
       expect(page).to have_content 'Delete'
       expect(page).to have_content 'Replace'
     end
@@ -390,7 +385,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
   end
 
   step 'I visit the SVG file' do
-    visit namespace_project_blob_path(@project.namespace, @project, 'new_branch_name/logo_sample.svg')
+    visit project_blob_path(@project, 'new_branch_name/logo_sample.svg')
   end
 
   step 'I can see the new rendered SVG image' do
diff --git a/features/steps/project/source/markdown_render.rb b/features/steps/project/source/markdown_render.rb
index cf31e61437ee720b0d398afbd4ca12c760da6afb..243a0f54f7f46ab85282645db89087cdb22647a8 100644
--- a/features/steps/project/source/markdown_render.rb
+++ b/features/steps/project/source/markdown_render.rb
@@ -14,7 +14,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
   end
 
   step 'I should see files from repository in markdown' do
-    expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, "markdown")
+    expect(current_path).to eq project_tree_path(@project, "markdown")
     expect(page).to have_content "README.md"
     expect(page).to have_content "CHANGELOG"
   end
@@ -34,7 +34,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
   end
 
   step 'I should see correct document rendered' do
-    expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md")
+    expect(current_path).to eq project_blob_path(@project, "markdown/doc/api/README.md")
     wait_for_requests
     expect(page).to have_content "All API requests require authentication"
   end
@@ -44,7 +44,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
   end
 
   step 'I should see correct directory rendered' do
-    expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, "markdown/doc/raketasks")
+    expect(current_path).to eq project_tree_path(@project, "markdown/doc/raketasks")
     expect(page).to have_content "backup_restore.md"
     expect(page).to have_content "maintenance.md"
   end
@@ -54,7 +54,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
   end
 
   step 'I should see correct doc/api directory rendered' do
-    expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, "markdown/doc/api")
+    expect(current_path).to eq project_tree_path(@project, "markdown/doc/api")
     expect(page).to have_content "README.md"
     expect(page).to have_content "users.md"
   end
@@ -64,7 +64,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
   end
 
   step 'I should see correct maintenance file rendered' do
-    expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/raketasks/maintenance.md")
+    expect(current_path).to eq project_blob_path(@project, "markdown/doc/raketasks/maintenance.md")
     wait_for_requests
     expect(page).to have_content "bundle exec rake gitlab:env:info RAILS_ENV=production"
   end
@@ -98,7 +98,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
   end
 
   step 'I see correct file rendered' do
-    expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md")
+    expect(current_path).to eq project_blob_path(@project, "markdown/doc/api/README.md")
     wait_for_requests
     expect(page).to have_content "Contents"
     expect(page).to have_link "Users"
@@ -110,7 +110,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
   end
 
   step 'I should see the correct document file' do
-    expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/users.md")
+    expect(current_path).to eq project_blob_path(@project, "markdown/doc/api/users.md")
     expect(page).to have_content "Get a list of users."
   end
 
@@ -121,30 +121,30 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
   # Markdown branch
 
   When 'I visit markdown branch' do
-    visit namespace_project_tree_path(@project.namespace, @project, "markdown")
+    visit project_tree_path(@project, "markdown")
     wait_for_requests
   end
 
   When 'I visit markdown branch "README.md" blob' do
-    visit namespace_project_blob_path(@project.namespace, @project, "markdown/README.md")
+    visit project_blob_path(@project, "markdown/README.md")
   end
 
   When 'I visit markdown branch "d" tree' do
-    visit namespace_project_tree_path(@project.namespace, @project, "markdown/d")
+    visit project_tree_path(@project, "markdown/d")
   end
 
   When 'I visit markdown branch "d/README.md" blob' do
-    visit namespace_project_blob_path(@project.namespace, @project, "markdown/d/README.md")
+    visit project_blob_path(@project, "markdown/d/README.md")
   end
 
   step 'I should see files from repository in markdown branch' do
-    expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, "markdown")
+    expect(current_path).to eq project_tree_path(@project, "markdown")
     expect(page).to have_content "README.md"
     expect(page).to have_content "CHANGELOG"
   end
 
   step 'I see correct file rendered in markdown branch' do
-    expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md")
+    expect(current_path).to eq project_blob_path(@project, "markdown/doc/api/README.md")
     wait_for_requests
     expect(page).to have_content "Contents"
     expect(page).to have_link "Users"
@@ -152,19 +152,19 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
   end
 
   step 'I should see correct document rendered for markdown branch' do
-    expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md")
+    expect(current_path).to eq project_blob_path(@project, "markdown/doc/api/README.md")
     wait_for_requests
     expect(page).to have_content "All API requests require authentication"
   end
 
   step 'I should see correct directory rendered for markdown branch' do
-    expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, "markdown/doc/raketasks")
+    expect(current_path).to eq project_tree_path(@project, "markdown/doc/raketasks")
     expect(page).to have_content "backup_restore.md"
     expect(page).to have_content "maintenance.md"
   end
 
   step 'I should see the users document file in markdown branch' do
-    expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/users.md")
+    expect(current_path).to eq project_blob_path(@project, "markdown/doc/api/users.md")
     expect(page).to have_content "Get a list of users."
   end
 
@@ -172,54 +172,54 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
 
   step 'The link with text "empty" should have url "tree/markdown"' do
     wait_for_requests
-    find('a', text: /^empty$/)['href'] == current_host + namespace_project_tree_path(@project.namespace, @project, "markdown")
+    find('a', text: /^empty$/)['href'] == current_host + project_tree_path(@project, "markdown")
   end
 
   step 'The link with text "empty" should have url "blob/markdown/README.md"' do
-    find('a', text: /^empty$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/README.md")
+    find('a', text: /^empty$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md")
   end
 
   step 'The link with text "empty" should have url "tree/markdown/d"' do
-    find('a', text: /^empty$/)['href'] == current_host + namespace_project_tree_path(@project.namespace, @project, "markdown/d")
+    find('a', text: /^empty$/)['href'] == current_host + project_tree_path(@project, "markdown/d")
   end
 
   step 'The link with text "empty" should have '\
        'url "blob/markdown/d/README.md"' do
-    find('a', text: /^empty$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/d/README.md")
+    find('a', text: /^empty$/)['href'] == current_host + project_blob_path(@project, "markdown/d/README.md")
   end
 
   step 'The link with text "ID" should have url "tree/markdownID"' do
-    find('a', text: /^#id$/)['href'] == current_host + namespace_project_tree_path(@project.namespace, @project, "markdown") + '#id'
+    find('a', text: /^#id$/)['href'] == current_host + project_tree_path(@project, "markdown") + '#id'
   end
 
   step 'The link with text "/ID" should have url "tree/markdownID"' do
-    find('a', text: /^\/#id$/)['href'] == current_host + namespace_project_tree_path(@project.namespace, @project, "markdown") + '#id'
+    find('a', text: /^\/#id$/)['href'] == current_host + project_tree_path(@project, "markdown") + '#id'
   end
 
   step 'The link with text "README.mdID" '\
        'should have url "blob/markdown/README.mdID"' do
-    find('a', text: /^README.md#id$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/README.md") + '#id'
+    find('a', text: /^README.md#id$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id'
   end
 
   step 'The link with text "d/README.mdID" should have '\
        'url "blob/markdown/d/README.mdID"' do
-    find('a', text: /^d\/README.md#id$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "d/markdown/README.md") + '#id'
+    find('a', text: /^d\/README.md#id$/)['href'] == current_host + project_blob_path(@project, "d/markdown/README.md") + '#id'
   end
 
   step 'The link with text "ID" should have url "blob/markdown/README.mdID"' do
     wait_for_requests
-    find('a', text: /^#id$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/README.md") + '#id'
+    find('a', text: /^#id$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id'
   end
 
   step 'The link with text "/ID" should have url "blob/markdown/README.mdID"' do
-    find('a', text: /^\/#id$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/README.md") + '#id'
+    find('a', text: /^\/#id$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id'
   end
 
   # Wiki
 
   step 'I go to wiki page' do
     click_link "Wiki"
-    expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "home")
+    expect(current_path).to eq project_wiki_path(@project, "home")
   end
 
   step 'I add various links to the wiki page' do
@@ -231,7 +231,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
   end
 
   step 'Wiki page should have added links' do
-    expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "home")
+    expect(current_path).to eq project_wiki_path(@project, "home")
     expect(page).to have_content "test GitLab API doc Rake tasks"
   end
 
@@ -252,7 +252,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
   end
 
   step 'I see new wiki page named test' do
-    expect(current_path).to eq  namespace_project_wiki_path(@project.namespace, @project, "test")
+    expect(current_path).to eq  project_wiki_path(@project, "test")
 
     page.within(:css, ".nav-text") do
       expect(page).to have_content "Test"
@@ -261,8 +261,8 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
   end
 
   When 'I go back to wiki page home' do
-    visit namespace_project_wiki_path(@project.namespace, @project, "home")
-    expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "home")
+    visit project_wiki_path(@project, "home")
+    expect(current_path).to eq project_wiki_path(@project, "home")
   end
 
   step 'I click on GitLab API doc link' do
@@ -270,7 +270,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
   end
 
   step 'I see Gitlab API document' do
-    expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "api")
+    expect(current_path).to eq project_wiki_path(@project, "api")
 
     page.within(:css, ".nav-text") do
       expect(page).to have_content "Create"
@@ -283,7 +283,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
   end
 
   step 'I see Rake tasks directory' do
-    expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "raketasks")
+    expect(current_path).to eq project_wiki_path(@project, "raketasks")
 
     page.within(:css, ".nav-text") do
       expect(page).to have_content "Create"
@@ -292,8 +292,8 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
   end
 
   step 'I go directory which contains README file' do
-    visit namespace_project_tree_path(@project.namespace, @project, "markdown/doc/api")
-    expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, "markdown/doc/api")
+    visit project_tree_path(@project, "markdown/doc/api")
+    expect(current_path).to eq project_tree_path(@project, "markdown/doc/api")
   end
 
   step 'I click on a relative link in README' do
@@ -301,7 +301,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
   end
 
   step 'I should see the correct markdown' do
-    expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/users.md")
+    expect(current_path).to eq project_blob_path(@project, "markdown/doc/api/users.md")
     wait_for_requests
     expect(page).to have_content "List users"
   end
diff --git a/features/steps/project/wiki.rb b/features/steps/project/wiki.rb
index 517c257d892d0e21e5dca4afac7544c2bf74b2c4..a2f5d2e1515825ca4a38511757898abf98eae279 100644
--- a/features/steps/project/wiki.rb
+++ b/features/steps/project/wiki.rb
@@ -11,7 +11,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
   end
 
   step 'I should be redirected back to the Edit Home Wiki page' do
-    expect(current_path).to eq namespace_project_wiki_path(project.namespace, project, :home)
+    expect(current_path).to eq project_wiki_path(project, :home)
   end
 
   step 'I create the Wiki Home page' do
@@ -42,7 +42,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
   end
 
   step 'I browse to that Wiki page' do
-    visit namespace_project_wiki_path(project.namespace, project, @page)
+    visit project_wiki_path(project, @page)
   end
 
   step 'I click on the Edit button' do
@@ -59,7 +59,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
   end
 
   step 'I should be redirected back to that Wiki page' do
-    expect(current_path).to eq namespace_project_wiki_path(project.namespace, project, @page)
+    expect(current_path).to eq project_wiki_path(project, @page)
   end
 
   step 'That page has two revisions' do
@@ -95,7 +95,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
   end
 
   step 'I browse to wiki page with images' do
-    visit namespace_project_wiki_path(project.namespace, project, @wiki_page)
+    visit project_wiki_path(project, @wiki_page)
   end
 
   step 'I click on existing image link' do
diff --git a/features/steps/shared/builds.rb b/features/steps/shared/builds.rb
index 624f1a7858bd2b76067b09d0e57db444b5eeb498..3b4c98ec00d935a56eb669fdcbe9ae32d144bcec 100644
--- a/features/steps/shared/builds.rb
+++ b/features/steps/shared/builds.rb
@@ -27,11 +27,11 @@ module SharedBuilds
   end
 
   step 'I visit recent build details page' do
-    visit namespace_project_job_path(@project.namespace, @project, @build)
+    visit project_job_path(@project, @build)
   end
 
   step 'I visit project builds page' do
-    visit namespace_project_jobs_path(@project.namespace, @project)
+    visit project_jobs_path(@project)
   end
 
   step 'recent build has artifacts available' do
@@ -56,7 +56,7 @@ module SharedBuilds
   end
 
   step 'I access artifacts download page' do
-    visit download_namespace_project_job_artifacts_path(@project.namespace, @project, @build)
+    visit download_project_job_artifacts_path(@project, @build)
   end
 
   step 'I see details of a build' do
diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb
index 36fc315599e3b99bfd19b741898304bc54bad988..2c59ec5bb060a0d1e40ebfc55f55953029d096e9 100644
--- a/features/steps/shared/diff_note.rb
+++ b/features/steps/shared/diff_note.rb
@@ -232,7 +232,7 @@ module SharedDiffNote
   end
 
   def click_parallel_diff_line(code, line_type)
-    find(".line_content.parallel.#{line_type}[data-line-code='#{code}']").trigger 'mouseover'
+    find(".line_holder.parallel .diff-line-num[id='#{code}']").trigger 'mouseover'
     find(".line_holder.parallel button[data-line-code='#{code}']").trigger 'click'
   end
 end
diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb
index 3d9cedf5c2d649b5e7b2a1c2fceeb327fe780241..7c842ba88fb853c7a31bd478cb3020c33ba63be0 100644
--- a/features/steps/shared/issuable.rb
+++ b/features/steps/shared/issuable.rb
@@ -51,22 +51,22 @@ module SharedIssuable
 
   step 'I visit issue page "Enterprise issue"' do
     issue = Issue.find_by(title: 'Enterprise issue')
-    visit namespace_project_issue_path(issue.project.namespace, issue.project, issue)
+    visit project_issue_path(issue.project, issue)
   end
 
   step 'I visit merge request page "Enterprise fix"' do
     mr = MergeRequest.find_by(title: 'Enterprise fix')
-    visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
+    visit project_merge_request_path(mr.target_project, mr)
   end
 
   step 'I visit issue page "Community issue"' do
     issue = Issue.find_by(title: 'Community issue')
-    visit namespace_project_issue_path(issue.project.namespace, issue.project, issue)
+    visit project_issue_path(issue.project, issue)
   end
 
   step 'I visit issue page "Community fix"' do
     mr = MergeRequest.find_by(title: 'Community fix')
-    visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
+    visit project_merge_request_path(mr.target_project, mr)
   end
 
   step 'I should not see any related merge requests' do
diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb
index f0e751b820a77f414b54dd8032b0bbd9e5870d0e..830263fd038df40c8750440e9e495a1f769ac3d5 100644
--- a/features/steps/shared/paths.rb
+++ b/features/steps/shared/paths.rb
@@ -112,10 +112,6 @@ module SharedPaths
     visit dashboard_groups_path
   end
 
-  step 'I visit dashboard todos page' do
-    visit dashboard_todos_path
-  end
-
   step 'I should be redirected to the dashboard groups page' do
     expect(current_path).to eq dashboard_groups_path
   end
@@ -205,67 +201,67 @@ module SharedPaths
   # ----------------------------------------
 
   step "I visit my project's home page" do
-    visit namespace_project_path(@project.namespace, @project)
+    visit project_path(@project)
   end
 
   step "I visit my project's settings page" do
-    visit edit_namespace_project_path(@project.namespace, @project)
+    visit edit_project_path(@project)
   end
 
   step "I visit my project's files page" do
-    visit namespace_project_tree_path(@project.namespace, @project, root_ref)
+    visit project_tree_path(@project, root_ref)
   end
 
   step 'I visit a binary file in the repo' do
-    visit namespace_project_blob_path(@project.namespace, @project,
+    visit project_blob_path(@project,
       File.join(root_ref, 'files/images/logo-black.png'))
   end
 
   step "I visit my project's commits page" do
-    visit namespace_project_commits_path(@project.namespace, @project, root_ref, { limit: 5 })
+    visit project_commits_path(@project, root_ref, { limit: 5 })
   end
 
   step "I visit my project's commits page for a specific path" do
-    visit namespace_project_commits_path(@project.namespace, @project, root_ref + "/app/models/project.rb", { limit: 5 })
+    visit project_commits_path(@project, root_ref + "/app/models/project.rb", { limit: 5 })
   end
 
   step 'I visit my project\'s commits stats page' do
-    visit stats_namespace_project_repository_path(@project.namespace, @project)
+    visit stats_project_repository_path(@project)
   end
 
   step "I visit my project's graph page" do
     # Stub Graph max_size to speed up test (10 commits vs. 650)
     Network::Graph.stub(max_count: 10)
 
-    visit namespace_project_network_path(@project.namespace, @project, root_ref)
+    visit project_network_path(@project, root_ref)
   end
 
   step "I visit my project's issues page" do
-    visit namespace_project_issues_path(@project.namespace, @project)
+    visit project_issues_path(@project)
   end
 
   step "I visit my project's merge requests page" do
-    visit namespace_project_merge_requests_path(@project.namespace, @project)
+    visit project_merge_requests_path(@project)
   end
 
   step "I visit my project's members page" do
-    visit namespace_project_project_members_path(@project.namespace, @project)
+    visit project_project_members_path(@project)
   end
 
   step "I visit my project's wiki page" do
-    visit namespace_project_wiki_path(@project.namespace, @project, :home)
+    visit project_wiki_path(@project, :home)
   end
 
   step 'I visit project hooks page' do
-    visit namespace_project_settings_integrations_path(@project.namespace, @project)
+    visit project_settings_integrations_path(@project)
   end
 
   step 'I visit project deploy keys page' do
-    visit namespace_project_deploy_keys_path(@project.namespace, @project)
+    visit project_deploy_keys_path(@project)
   end
 
   step 'I visit project find file page' do
-    visit namespace_project_find_file_path(@project.namespace, @project, root_ref)
+    visit project_find_file_path(@project, root_ref)
   end
 
   # ----------------------------------------
@@ -273,107 +269,107 @@ module SharedPaths
   # ----------------------------------------
 
   step 'I visit project "Shop" page' do
-    visit namespace_project_path(project.namespace, project)
+    visit project_path(project)
   end
 
   step 'I visit project "Shop" activity page' do
-    visit activity_namespace_project_path(project.namespace, project)
+    visit activity_project_path(project)
   end
 
   step 'I visit project "Forked Shop" merge requests page' do
-    visit namespace_project_merge_requests_path(@forked_project.namespace, @forked_project)
+    visit project_merge_requests_path(@forked_project)
   end
 
   step 'I visit edit project "Shop" page' do
-    visit edit_namespace_project_path(project.namespace, project)
+    visit edit_project_path(project)
   end
 
   step 'I visit project branches page' do
-    visit namespace_project_branches_path(@project.namespace, @project)
+    visit project_branches_path(@project)
   end
 
   step 'I visit project protected branches page' do
-    visit namespace_project_protected_branches_path(@project.namespace, @project)
+    visit project_protected_branches_path(@project)
   end
 
   step 'I visit compare refs page' do
-    visit namespace_project_compare_index_path(@project.namespace, @project)
+    visit project_compare_index_path(@project)
   end
 
   step 'I visit project commits page' do
-    visit namespace_project_commits_path(@project.namespace, @project, root_ref, { limit: 5 })
+    visit project_commits_path(@project, root_ref, { limit: 5 })
   end
 
   step 'I visit project commits page for stable branch' do
-    visit namespace_project_commits_path(@project.namespace, @project, 'stable', { limit: 5 })
+    visit project_commits_path(@project, 'stable', { limit: 5 })
   end
 
   step 'I visit project source page' do
-    visit namespace_project_tree_path(@project.namespace, @project, root_ref)
+    visit project_tree_path(@project, root_ref)
   end
 
   step 'I visit blob file from repo' do
-    visit namespace_project_blob_path(@project.namespace, @project, File.join(sample_commit.id, sample_blob.path))
+    visit project_blob_path(@project, File.join(sample_commit.id, sample_blob.path))
   end
 
   step 'I visit ".gitignore" file in repo' do
-    visit namespace_project_blob_path(@project.namespace, @project, File.join(root_ref, '.gitignore'))
+    visit project_blob_path(@project, File.join(root_ref, '.gitignore'))
   end
 
   step 'I am on the new file page' do
-    expect(current_path).to eq(namespace_project_create_blob_path(@project.namespace, @project, root_ref))
+    expect(current_path).to eq(project_create_blob_path(@project, root_ref))
   end
 
   step 'I am on the ".gitignore" edit file page' do
     expect(current_path).to eq(
-      namespace_project_edit_blob_path(@project.namespace, @project, File.join(root_ref, '.gitignore')))
+      project_edit_blob_path(@project, File.join(root_ref, '.gitignore')))
   end
 
   step 'I visit project source page for "6d39438"' do
-    visit namespace_project_tree_path(@project.namespace, @project, "6d39438")
+    visit project_tree_path(@project, "6d39438")
   end
 
   step 'I visit project source page for' \
        ' "6d394385cf567f80a8fd85055db1ab4c5295806f"' do
-    visit namespace_project_tree_path(@project.namespace, @project,
+    visit project_tree_path(@project,
                             '6d394385cf567f80a8fd85055db1ab4c5295806f')
   end
 
   step 'I visit project tags page' do
-    visit namespace_project_tags_path(@project.namespace, @project)
+    visit project_tags_path(@project)
   end
 
   step 'I visit project commit page' do
-    visit namespace_project_commit_path(@project.namespace, @project, sample_commit.id)
+    visit project_commit_path(@project, sample_commit.id)
   end
 
   step 'I visit project "Shop" issues page' do
-    visit namespace_project_issues_path(project.namespace, project)
+    visit project_issues_path(project)
   end
 
   step 'I visit issue page "Release 0.4"' do
     issue = Issue.find_by(title: "Release 0.4")
-    visit namespace_project_issue_path(issue.project.namespace, issue.project, issue)
+    visit project_issue_path(issue.project, issue)
   end
 
   step 'I visit project "Shop" labels page' do
     project = Project.find_by(name: 'Shop')
-    visit namespace_project_labels_path(project.namespace, project)
+    visit project_labels_path(project)
   end
 
   step 'I visit project "Forum" labels page' do
     project = Project.find_by(name: 'Forum')
-    visit namespace_project_labels_path(project.namespace, project)
+    visit project_labels_path(project)
   end
 
   step 'I visit project "Shop" new label page' do
     project = Project.find_by(name: 'Shop')
-    visit new_namespace_project_label_path(project.namespace, project)
+    visit new_project_label_path(project)
   end
 
   step 'I visit project "Forum" new label page' do
     project = Project.find_by(name: 'Forum')
-    visit new_namespace_project_label_path(project.namespace, project)
+    visit new_project_label_path(project)
   end
 
   step 'I visit merge request page "Bug NS-04"' do
@@ -398,28 +394,28 @@ module SharedPaths
 
   step 'I visit merge request page "Bug CO-01"' do
     mr = MergeRequest.find_by(title: "Bug CO-01")
-    visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
+    visit project_merge_request_path(mr.target_project, mr)
     wait_for_requests
   end
 
   step 'I visit project "Shop" merge requests page' do
-    visit namespace_project_merge_requests_path(project.namespace, project)
+    visit project_merge_requests_path(project)
   end
 
   step 'I visit forked project "Shop" merge requests page' do
-    visit namespace_project_merge_requests_path(project.namespace, project)
+    visit project_merge_requests_path(project)
   end
 
   step 'I visit project "Shop" milestones page' do
-    visit namespace_project_milestones_path(project.namespace, project)
+    visit project_milestones_path(project)
   end
 
   step 'I visit project "Shop" team page' do
-    visit namespace_project_project_members_path(project.namespace, project)
+    visit project_project_members_path(project)
   end
 
   step 'I visit project wiki page' do
-    visit namespace_project_wiki_path(@project.namespace, @project, :home)
+    visit project_wiki_path(@project, :home)
   end
 
   # ----------------------------------------
@@ -428,22 +424,22 @@ module SharedPaths
 
   step 'I visit project "Community" page' do
     project = Project.find_by(name: "Community")
-    visit namespace_project_path(project.namespace, project)
+    visit project_path(project)
   end
 
   step 'I visit project "Community" source page' do
     project = Project.find_by(name: 'Community')
-    visit namespace_project_tree_path(project.namespace, project, root_ref)
+    visit project_tree_path(project, root_ref)
   end
 
   step 'I visit project "Internal" page' do
     project = Project.find_by(name: "Internal")
-    visit namespace_project_path(project.namespace, project)
+    visit project_path(project)
   end
 
   step 'I visit project "Enterprise" page' do
     project = Project.find_by(name: "Enterprise")
-    visit namespace_project_path(project.namespace, project)
+    visit project_path(project)
   end
 
   # ----------------------------------------
@@ -452,7 +448,7 @@ module SharedPaths
 
   step "I visit empty project page" do
     project = Project.find_by(name: "Empty Public Project")
-    visit namespace_project_path(project.namespace, project)
+    visit project_path(project)
   end
 
   step "I should not see command line instructions" do
@@ -484,17 +480,13 @@ module SharedPaths
   # ----------------------------------------
 
   step 'I visit project "Shop" snippets page' do
-    visit namespace_project_snippets_path(project.namespace, project)
+    visit project_snippets_path(project)
   end
 
   step 'I visit snippets page' do
     visit explore_snippets_path
   end
 
-  step 'I visit new snippet page' do
-    visit new_snippet_path
-  end
-
   def root_ref
     @project.repository.root_ref
   end
@@ -505,7 +497,7 @@ module SharedPaths
 
   def merge_request_path(title)
     mr = MergeRequest.find_by(title: title)
-    namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
+    project_merge_request_path(mr.target_project, mr)
   end
 
   # ----------------------------------------
diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb
index c4f1c57836f8e6d76aa94b916970e7c0c3aa54f3..729e2b8982c32624ed2af45f4ffcfd8623325077 100644
--- a/features/steps/shared/project.rb
+++ b/features/steps/shared/project.rb
@@ -61,12 +61,12 @@ module SharedProject
 
   step 'I visit my empty project page' do
     project = Project.find_by(name: 'Empty Project')
-    visit namespace_project_path(project.namespace, project)
+    visit project_path(project)
   end
 
   step 'I visit project "Shop" activity page' do
     project = Project.find_by(name: 'Shop')
-    visit namespace_project_path(project.namespace, project)
+    visit project_path(project)
   end
 
   step 'project "Shop" has push event' do
@@ -101,7 +101,7 @@ module SharedProject
   end
 
   step 'I should see project settings' do
-    expect(current_path).to eq edit_namespace_project_path(@project.namespace, @project)
+    expect(current_path).to eq edit_project_path(@project)
     expect(page).to have_content("Project name")
     expect(page).to have_content("Sharing & Permissions")
   end
diff --git a/features/steps/shared/snippet.rb b/features/steps/shared/snippet.rb
deleted file mode 100644
index bb596c1620a2ae32a97d7aceaa5d255f3051878e..0000000000000000000000000000000000000000
--- a/features/steps/shared/snippet.rb
+++ /dev/null
@@ -1,63 +0,0 @@
-module SharedSnippet
-  include Spinach::DSL
-
-  step 'I have public "Personal snippet one" snippet' do
-    create(:personal_snippet,
-           title: "Personal snippet one",
-           content: "Test content",
-           file_name: "snippet.rb",
-           visibility_level: Snippet::PUBLIC,
-           author: current_user)
-  end
-
-  step 'I have private "Personal snippet private" snippet' do
-    create(:personal_snippet,
-           title: "Personal snippet private",
-           content: "Provate content",
-           file_name: "private_snippet.rb",
-           visibility_level: Snippet::PRIVATE,
-           author: current_user)
-  end
-  
-  step 'I have internal "Personal snippet internal" snippet' do
-    create(:personal_snippet,
-           title: "Personal snippet internal",
-           content: "Provate content",
-           file_name: "internal_snippet.rb",
-           visibility_level: Snippet::INTERNAL,
-           author: current_user)
-  end
-  
-  step 'I have a public many lined snippet' do
-    create(:personal_snippet,
-           title: 'Many lined snippet',
-           content: <<-END.gsub(/^\s+\|/, ''),
-             |line one
-             |line two
-             |line three
-             |line four
-             |line five
-             |line six
-             |line seven
-             |line eight
-             |line nine
-             |line ten
-             |line eleven
-             |line twelve
-             |line thirteen
-             |line fourteen
-           END
-           file_name: 'many_lined_snippet.rb',
-           visibility_level: Snippet::PUBLIC,
-           author: current_user)
-  end
-
-  step 'There is public "Personal snippet one" snippet' do
-    create(:personal_snippet,
-           title: "Personal snippet one",
-           content: "Test content",
-           file_name: "snippet.rb",
-           visibility_level: Snippet::PUBLIC,
-           author: create(:user))
-  end
-end
diff --git a/features/steps/snippets/snippets.rb b/features/steps/snippets/snippets.rb
deleted file mode 100644
index a4fc77746ee387069b9407ad709da75c447af5e0..0000000000000000000000000000000000000000
--- a/features/steps/snippets/snippets.rb
+++ /dev/null
@@ -1,86 +0,0 @@
-class Spinach::Features::Snippets < Spinach::FeatureSteps
-  include SharedAuthentication
-  include SharedPaths
-  include SharedProject
-  include SharedSnippet
-  include WaitForRequests
-
-  step 'I click link "Personal snippet one"' do
-    click_link "Personal snippet one"
-  end
-
-  step 'I should not see "Personal snippet one" in snippets' do
-    expect(page).not_to have_content "Personal snippet one"
-  end
-
-  step 'I click link "Edit"' do
-    page.within ".detail-page-header" do
-      first(:link, "Edit").click
-    end
-  end
-
-  step 'I click link "Delete"' do
-    first(:link, "Delete").click
-  end
-
-  step 'I submit new snippet "Personal snippet three"' do
-    fill_in "personal_snippet_title", with: "Personal snippet three"
-    fill_in "personal_snippet_file_name", with: "my_snippet.rb"
-    page.within('.file-editor') do
-      find('.ace_editor').native.send_keys 'Content of snippet three'
-    end
-    click_button "Create snippet"
-    wait_for_requests
-  end
-
-  step 'I submit new internal snippet' do
-    fill_in "personal_snippet_title", with: "Internal personal snippet one"
-    fill_in "personal_snippet_file_name", with: "my_snippet.rb"
-    choose 'personal_snippet_visibility_level_10'
-
-    page.within('.file-editor') do
-      find(:xpath, "//input[@id='personal_snippet_content']").set 'Content of internal snippet'
-    end
-
-    click_button "Create snippet"
-  end
-
-  step 'I should see snippet "Personal snippet three"' do
-    expect(page).to have_content "Personal snippet three"
-    expect(page).to have_content "Content of snippet three"
-  end
-
-  step 'I submit new title "Personal snippet new title"' do
-    fill_in "personal_snippet_title", with: "Personal snippet new title"
-    click_button "Save"
-  end
-
-  step 'I should see "Personal snippet new title"' do
-    expect(page).to have_content "Personal snippet new title"
-  end
-
-  step 'I uncheck "Private" checkbox' do
-    choose "Internal"
-    click_button "Save"
-  end
-
-  step 'I should see "Personal snippet one" public' do
-    expect(page).to have_no_xpath("//i[@class='public-snippet']")
-  end
-
-  step 'I visit snippet page "Personal snippet one"' do
-    visit snippet_path(snippet)
-  end
-
-  step 'I visit snippet page "Internal personal snippet one"' do
-    visit snippet_path(internal_snippet)
-  end
-
-  def snippet
-    @snippet ||= PersonalSnippet.find_by!(title: "Personal snippet one")
-  end
-
-  def internal_snippet
-    @snippet ||= PersonalSnippet.find_by!(title: "Internal personal snippet one")
-  end
-end
diff --git a/features/support/env.rb b/features/support/env.rb
index 1690465d9b29fee5ce40846ea98908ed1f9908c6..608d988755c6de2f0cddd3e0426d7d683967dea0 100644
--- a/features/support/env.rb
+++ b/features/support/env.rb
@@ -28,6 +28,7 @@ Spinach.hooks.before_run do
   TestEnv.disable_pre_receive
 
   include FactoryGirl::Syntax::Methods
+  include GitlabRoutingHelper
 end
 
 Spinach.hooks.after_scenario do |scenario_data, step_definitions|
diff --git a/lib/api/access_requests.rb b/lib/api/access_requests.rb
index a5c9f0b509c39287540ccde133ac4d9453118df9..c9b5f58c55796555bc8c0a9ba5b967b9b69cc747 100644
--- a/lib/api/access_requests.rb
+++ b/lib/api/access_requests.rb
@@ -68,8 +68,8 @@ module API
         delete ":id/access_requests/:user_id" do
           source = find_source(source_type, params[:id])
 
-          ::Members::DestroyService.new(source, current_user, params).
-            execute(:requesters)
+          ::Members::DestroyService.new(source, current_user, params)
+            .execute(:requesters)
         end
       end
     end
diff --git a/lib/api/api.rb b/lib/api/api.rb
index d767af36e8e84707ad7c1f9103bc267d88c207cf..efcf0976a819d097589ab2a1f48fc7293f0cda01 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -2,6 +2,8 @@ module API
   class API < Grape::API
     include APIGuard
 
+    allow_access_with_scope :api
+
     version %w(v3 v4), using: :path
 
     version 'v3', using: :path do
@@ -44,7 +46,6 @@ module API
       mount ::API::V3::Variables
     end
 
-    before { allow_access_with_scope :api }
     before { header['X-Frame-Options'] = 'SAMEORIGIN' }
     before { Gitlab::I18n.locale = current_user&.preferred_language }
 
diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb
index 9fcf04efa38aa69cab202141d20af935de943f5f..0d2d71e336a10b27b1e6551435b9822374f3e207 100644
--- a/lib/api/api_guard.rb
+++ b/lib/api/api_guard.rb
@@ -23,6 +23,23 @@ module API
       install_error_responders(base)
     end
 
+    class_methods do
+      # Set the authorization scope(s) allowed for an API endpoint.
+      #
+      # A call to this method maps the given scope(s) to the current API
+      # endpoint class. If this method is called multiple times on the same class,
+      # the scopes are all aggregated.
+      def allow_access_with_scope(scopes, options = {})
+        Array(scopes).each do |scope|
+          allowed_scopes << Scope.new(scope, options)
+        end
+      end
+
+      def allowed_scopes
+        @scopes ||= []
+      end
+    end
+
     # Helper Methods for Grape Endpoint
     module HelperMethods
       # Invokes the doorkeeper guard.
@@ -47,7 +64,7 @@ module API
         access_token = find_access_token
         return nil unless access_token
 
-        case AccessTokenValidationService.new(access_token).validate(scopes: scopes)
+        case AccessTokenValidationService.new(access_token, request: request).validate(scopes: scopes)
         when AccessTokenValidationService::INSUFFICIENT_SCOPE
           raise InsufficientScopeError.new(scopes)
 
@@ -74,18 +91,6 @@ module API
         @current_user
       end
 
-      # Set the authorization scope(s) allowed for the current request.
-      #
-      # Note: A call to this method adds to any previous scopes in place. This is done because
-      # `Grape` callbacks run from the outside-in: the top-level callback (API::API) runs first, then
-      # the next-level callback (API::API::Users, for example) runs. All these scopes are valid for the
-      # given endpoint (GET `/api/users` is accessible by the `api` and `read_user` scopes), and so they
-      # need to be stored.
-      def allow_access_with_scope(*scopes)
-        @scopes ||= []
-        @scopes.concat(scopes.map(&:to_s))
-      end
-
       private
 
       def find_user_by_authentication_token(token_string)
@@ -96,7 +101,7 @@ module API
         access_token = PersonalAccessToken.active.find_by_token(token_string)
         return unless access_token
 
-        if AccessTokenValidationService.new(access_token).include_any_scope?(scopes)
+        if AccessTokenValidationService.new(access_token, request: request).include_any_scope?(scopes)
           User.find(access_token.user_id)
         end
       end
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index f35084a582ac286f5f69f8f2b1c42c996d975bf5..3d816f8771db32344b22aa3e56d8d2b96df95925 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -102,8 +102,8 @@ module API
       post ":id/repository/branches" do
         authorize_push_project
 
-        result = CreateBranchService.new(user_project, current_user).
-                 execute(params[:branch], params[:ref])
+        result = CreateBranchService.new(user_project, current_user)
+                 .execute(params[:branch], params[:ref])
 
         if result[:status] == :success
           present result[:branch],
@@ -121,8 +121,8 @@ module API
       delete ":id/repository/branches/:branch", requirements: { branch: /.+/ } do
         authorize_push_project
 
-        result = DeleteBranchService.new(user_project, current_user).
-                 execute(params[:branch])
+        result = DeleteBranchService.new(user_project, current_user)
+                 .execute(params[:branch])
 
         if result[:status] != :success
           render_api_error!(result[:message], result[:return_code])
diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb
index 10f2d5ef6a35c61b7419c5c453ae3f9c6e8cdfcd..485b680cd5fddbdf73e5bc004fe5ee90b6ed120e 100644
--- a/lib/api/commit_statuses.rb
+++ b/lib/api/commit_statuses.rb
@@ -108,6 +108,9 @@ module API
             render_api_error!('invalid state', 400)
           end
 
+          MergeRequest.where(source_project: @project, source_branch: ref)
+            .update_all(head_pipeline_id: pipeline) if pipeline.latest?
+
           present status, with: Entities::CommitStatus
         rescue StateMachines::InvalidTransition => e
           render_api_error!(e.message, 400)
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index c6fc17cc391c2afdaf358dfa7a65bd225eca1db7..bcb842b9211b343f76ed097b6dca345abd0056c3 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -67,7 +67,7 @@ module API
         result = ::Files::MultiService.new(user_project, current_user, attrs).execute
 
         if result[:status] == :success
-          commit_detail = user_project.repository.commits(result[:result], limit: 1).first
+          commit_detail = user_project.repository.commit(result[:result])
           present commit_detail, with: Entities::RepoCommitDetail
         else
           render_api_error!(result[:message], 400)
diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb
index 7cdee8aced7659392448684919ed628ef6e8757d..d5c2f3d509448efc6dd9e4d23651db5632100a97 100644
--- a/lib/api/deploy_keys.rb
+++ b/lib/api/deploy_keys.rb
@@ -86,7 +86,7 @@ module API
         at_least_one_of :title, :can_push
       end
       put ":id/deploy_keys/:key_id" do
-        key = user_project.deploy_keys.find(params.delete(:key_id))
+        key = DeployKey.find(params.delete(:key_id))
 
         authorize!(:update_deploy_key, key)
 
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 412443a2405fb0c5fa44fc02f930c4fb4a3fd86f..99eda3b0c4b48ecf3750f3d52dd94a1bcf182559 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -43,11 +43,14 @@ module API
       expose :external
     end
 
-    class UserWithPrivateDetails < UserPublic
-      expose :private_token
+    class UserWithAdmin < UserPublic
       expose :admin?, as: :is_admin
     end
 
+    class UserWithPrivateDetails < UserWithAdmin
+      expose :private_token
+    end
+
     class Email < Grape::Entity
       expose :id, :email
     end
@@ -109,12 +112,14 @@ module API
       expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:current_user]) && project.default_issues_tracker? }
       expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] }
       expose :public_builds, as: :public_jobs
+      expose :ci_config_path
       expose :shared_with_groups do |project, options|
         SharedGroup.represent(project.project_group_links.all, options)
       end
       expose :only_allow_merge_if_pipeline_succeeds
       expose :request_access_enabled
       expose :only_allow_merge_if_all_discussions_are_resolved
+      expose :printing_merge_request_link_enabled
 
       expose :statistics, using: 'API::Entities::ProjectStatistics', if: :statistics
     end
@@ -430,7 +435,7 @@ module API
         target_url    = "namespace_project_#{target_type}_url"
         target_anchor = "note_#{todo.note_id}" if todo.note_id?
 
-        Gitlab::Application.routes.url_helpers.public_send(target_url,
+        Gitlab::Routing.url_helpers.public_send(target_url,
           todo.project.namespace, todo.project, todo.target, anchor: target_anchor)
       end
 
@@ -440,7 +445,15 @@ module API
     end
 
     class Namespace < Grape::Entity
-      expose :id, :name, :path, :kind, :full_path
+      expose :id, :name, :path, :kind, :full_path, :parent_id
+
+      expose :members_count_with_descendants, if: -> (namespace, opts) { expose_members_count_with_descendants?(namespace, opts) } do |namespace, _|
+        namespace.users_with_descendants.count
+      end
+
+      def expose_members_count_with_descendants?(namespace, opts)
+        namespace.kind == 'group' && Ability.allowed?(opts[:current_user], :admin_group, namespace)
+      end
     end
 
     class MemberAccess < Grape::Entity
@@ -480,9 +493,9 @@ module API
       expose :job_events
       # Expose serialized properties
       expose :properties do |service, options|
-        field_names = service.fields.
-          select { |field| options[:include_passwords] || field[:type] != 'password' }.
-          map { |field| field[:name] }
+        field_names = service.fields
+          .select { |field| options[:include_passwords] || field[:type] != 'password' }
+          .map { |field| field[:name] }
         service.properties.slice(*field_names)
       end
     end
@@ -819,7 +832,7 @@ module API
       end
 
       class Cache < Grape::Entity
-        expose :key, :untracked, :paths
+        expose :key, :untracked, :paths, :policy
       end
 
       class Credentials < Grape::Entity
diff --git a/lib/api/features.rb b/lib/api/features.rb
index cff0ba2ddff3f29fd0df2a0a0fe0c5ab24c8a9d2..217459164638068adbf9d6bf166c1793edcffd0e 100644
--- a/lib/api/features.rb
+++ b/lib/api/features.rb
@@ -2,6 +2,29 @@ module API
   class Features < Grape::API
     before { authenticated_as_admin! }
 
+    helpers do
+      def gate_value(params)
+        case params[:value]
+        when 'true'
+          true
+        when '0', 'false'
+          false
+        else
+          params[:value].to_i
+        end
+      end
+
+      def gate_target(params)
+        if params[:feature_group]
+          Feature.group(params[:feature_group])
+        elsif params[:user]
+          User.find_by_username(params[:user])
+        else
+          gate_value(params)
+        end
+      end
+    end
+
     resource :features do
       desc 'Get a list of all features' do
         success Entities::Feature
@@ -17,16 +40,22 @@ module API
       end
       params do
         requires :value, type: String, desc: '`true` or `false` to enable/disable, an integer for percentage of time'
+        optional :feature_group, type: String, desc: 'A Feature group name'
+        optional :user, type: String, desc: 'A GitLab username'
+        mutually_exclusive :feature_group, :user
       end
       post ':name' do
         feature = Feature.get(params[:name])
+        target = gate_target(params)
+        value = gate_value(params)
 
-        if %w(0 false).include?(params[:value])
-          feature.disable
-        elsif params[:value] == 'true'
-          feature.enable
+        case value
+        when true
+          feature.enable(target)
+        when false
+          feature.disable(target)
         else
-          feature.enable_percentage_of_time(params[:value].to_i)
+          feature.enable_percentage_of_time(value)
         end
 
         present feature, with: Entities::Feature, current_user: current_user
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 2c73a6fdc4ecf1813be6d53f3fac1595a526b61a..a2a661b205c6a15cc6de0a4aa4c51f20a990b44a 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -342,8 +342,8 @@ module API
     def initial_current_user
       return @initial_current_user if defined?(@initial_current_user)
       Gitlab::Auth::UniqueIpsLimiter.limit_user! do
-        @initial_current_user ||= find_user_by_private_token(scopes: @scopes)
-        @initial_current_user ||= doorkeeper_guard(scopes: @scopes)
+        @initial_current_user ||= find_user_by_private_token(scopes: scopes_registered_for_endpoint)
+        @initial_current_user ||= doorkeeper_guard(scopes: scopes_registered_for_endpoint)
         @initial_current_user ||= find_user_from_warden
 
         unless @initial_current_user && Gitlab::UserAccess.new(@initial_current_user).allowed?
@@ -407,5 +407,22 @@ module API
 
       exception.status == 500
     end
+
+    # An array of scopes that were registered (using `allow_access_with_scope`)
+    # for the current endpoint class. It also returns scopes registered on
+    # `API::API`, since these are meant to apply to all API routes.
+    def scopes_registered_for_endpoint
+      @scopes_registered_for_endpoint ||=
+        begin
+          endpoint_classes = [options[:for].presence, ::API::API].compact
+          endpoint_classes.reduce([]) do |memo, endpoint|
+            if endpoint.respond_to?(:allowed_scopes)
+              memo.concat(endpoint.allowed_scopes)
+            else
+              memo
+            end
+          end
+        end
+    end
   end
 end
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index d3732d67622933e2d60aa18792b6558af6c01a20..5e9cf5e68b11baec287fe9cc1284c10485b733c5 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -10,6 +10,10 @@ module API
         set_project unless defined?(@project)
         @project
       end
+      
+      def redirected_path
+        @redirected_path
+      end
 
       def ssh_authentication_abilities
         [
@@ -38,8 +42,9 @@ module API
       def set_project
         if params[:gl_repository]
           @project, @wiki = Gitlab::GlRepository.parse(params[:gl_repository])
+          @redirected_path = nil
         else
-          @project, @wiki = Gitlab::RepoPath.parse(params[:project])
+          @project, @wiki, @redirected_path = Gitlab::RepoPath.parse(params[:project])
         end
       end
 
diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb
index 1369b021ea4768345495e61261e17f32886361aa..f8645e364cec020cda01777959315a14a1890964 100644
--- a/lib/api/helpers/runner.rb
+++ b/lib/api/helpers/runner.rb
@@ -46,7 +46,8 @@ module API
 
         yield if block_given?
 
-        forbidden!('Project has been deleted!') unless job.project
+        project = job.project
+        forbidden!('Project has been deleted!') if project.nil? || project.pending_delete?
         forbidden!('Job has been erased!') if job.erased?
       end
 
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index ecd6d672cf7fca2fd4ff97035abf9c0872d35b66..f1c79970ba4e3953b491be830809b022f22e809d 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -34,7 +34,7 @@ module API
 
         access_checker_klass = wiki? ? Gitlab::GitAccessWiki : Gitlab::GitAccess
         access_checker = access_checker_klass
-          .new(actor, project, protocol, authentication_abilities: ssh_authentication_abilities)
+          .new(actor, project, protocol, authentication_abilities: ssh_authentication_abilities, redirected_path: redirected_path)
 
         begin
           access_checker.check(params[:action], params[:changes])
@@ -71,11 +71,16 @@ module API
       end
 
       #
-      # Discover user by ssh key
+      # Discover user by ssh key or user id
       #
       get "/discover" do
-        key = Key.find(params[:key_id])
-        present key.user, with: Entities::UserSafe
+        if params[:key_id]
+          key = Key.find(params[:key_id])
+          user = key.user
+        elsif params[:user_id]
+          user = User.find_by(id: params[:user_id])
+        end
+        present user, with: Entities::UserSafe
       end
 
       get "/check" do
@@ -127,8 +132,11 @@ module API
           return { success: false, message: 'Two-factor authentication is not enabled for this user' }
         end
 
-        codes = user.generate_otp_backup_codes!
-        user.save!
+        codes = nil
+
+        ::Users::UpdateService.new(user).execute! do |user|
+          codes = user.generate_otp_backup_codes!
+        end
 
         { success: true, recovery_codes: codes }
       end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 78db960ae28fa2b8499306bfcfc88aa5d9b3454b..09dca0dff8bc04f9d770aa22394561e1ed3acae0 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -27,6 +27,8 @@ module API
         optional :milestone, type: String, desc: 'Return issues for a specific milestone'
         optional :iids, type: Array[Integer], desc: 'The IID array of issues'
         optional :search, type: String, desc: 'Search issues for text present in the title or description'
+        optional :created_after, type: DateTime, desc: 'Return issues created after the specified time'
+        optional :created_before, type: DateTime, desc: 'Return issues created before the specified time'
         use :pagination
       end
 
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 710deba5ae3f230ae0e115d380bca69db23d1a72..1118fc7465b6209c5d3f91c3ee0740d1b35d8697 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -72,6 +72,8 @@ module API
         optional :iids, type: Array[Integer], desc: 'The IID array of merge requests'
         optional :milestone, type: String, desc: 'Return merge requests for a specific milestone'
         optional :labels, type: String, desc: 'Comma-separated list of label names'
+        optional :created_after, type: DateTime, desc: 'Return merge requests created after the specified time'
+        optional :created_before, type: DateTime, desc: 'Return merge requests created before the specified time'
         use :pagination
       end
       get ":id/merge_requests" do
@@ -97,7 +99,7 @@ module API
         authorize! :create_merge_request, user_project
 
         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?
+        mr_params[:force_remove_source_branch] = mr_params.delete(:remove_source_branch)
 
         merge_request = ::MergeRequests::CreateService.new(user_project, current_user, mr_params).execute
 
diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb
index a3ea619a2fbafcde0d17a89ae2d30ba6d32d54f5..3541d3c95fb616339cb54624116a2aa8d17352cf 100644
--- a/lib/api/milestones.rb
+++ b/lib/api/milestones.rb
@@ -117,7 +117,7 @@ module API
         finder_params = {
           project_id: user_project.id,
           milestone_title: milestone.title,
-          sort: 'position_asc'
+          sort: 'label_priority'
         }
 
         issues = IssuesFinder.new(current_user, finder_params).execute
@@ -140,7 +140,7 @@ module API
         finder_params = {
           project_id: user_project.id,
           milestone_title: milestone.title,
-          sort: 'position_asc'
+          sort: 'label_priority'
         }
 
         merge_requests = MergeRequestsFinder.new(current_user, finder_params).execute
diff --git a/lib/api/namespaces.rb b/lib/api/namespaces.rb
index 30761cb9b5571c54bd90d25a6c0fdd4269a4e1e6..f1eaff6b0ebd1a0ed62b23a1a74f3b56068348bc 100644
--- a/lib/api/namespaces.rb
+++ b/lib/api/namespaces.rb
@@ -17,7 +17,7 @@ module API
 
         namespaces = namespaces.search(params[:search]) if params[:search].present?
 
-        present paginate(namespaces), with: Entities::Namespace
+        present paginate(namespaces), with: Entities::Namespace, current_user: current_user
       end
     end
   end
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index e281e3230fdab9766f44bc8c535e358f9004d266..01ca62b593f5b158d03e5f8db88320bb22b775a2 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -33,8 +33,8 @@ module API
               # paginate() only works with a relation. This could lead to a
               # mismatch between the pagination headers info and the actual notes
               # array returned, but this is really a edge-case.
-              paginate(noteable.notes).
-              reject { |n| n.cross_reference_not_visible_for?(current_user) }
+              paginate(noteable.notes)
+              .reject { |n| n.cross_reference_not_visible_for?(current_user) }
             present notes, with: Entities::Note
           else
             not_found!("Notes")
diff --git a/lib/api/notification_settings.rb b/lib/api/notification_settings.rb
index 992ea5dc24de8a5cf46be9733c220fa24f98e7b9..5d113c94b224e80ddabaa85e0a6883a6eee6ca08 100644
--- a/lib/api/notification_settings.rb
+++ b/lib/api/notification_settings.rb
@@ -34,7 +34,10 @@ module API
           notification_setting.transaction do
             new_notification_email = params.delete(:notification_email)
 
-            current_user.update(notification_email: new_notification_email) if new_notification_email
+            if new_notification_email
+              ::Users::UpdateService.new(current_user, notification_email: new_notification_email).execute
+            end
+
             notification_setting.update(declared_params(include_missing: false))
           end
         rescue ArgumentError => e # catch level enum error
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 50d34e8a7382a6958233c8351033db38ecfc20b4..35733ac77111ebf07dc1613ee3a5f770118644b2 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -1,3 +1,5 @@
+require_dependency 'declarative_policy'
+
 module API
   # Projects API
   class Projects < Grape::API
@@ -8,6 +10,7 @@ module API
     helpers do
       params :optional_params_ce do
         optional :description, type: String, desc: 'The description of the project'
+        optional :ci_config_path, type: String, desc: 'The path to CI config file. Defaults to `.gitlab-ci.yml`'
         optional :issues_enabled, type: Boolean, desc: 'Flag indication if the issue tracker is enabled'
         optional :merge_requests_enabled, type: Boolean, desc: 'Flag indication if merge requests are enabled'
         optional :wiki_enabled, type: Boolean, desc: 'Flag indication if the wiki is enabled'
@@ -23,6 +26,7 @@ module API
         optional :only_allow_merge_if_all_discussions_are_resolved, type: Boolean, desc: 'Only allow to merge if all discussions are resolved'
         optional :tag_list, type: Array[String], desc: 'The list of tags for a project'
         optional :avatar, type: File, desc: 'Avatar image for project'
+        optional :printing_merge_request_link_enabled, type: Boolean, desc: 'Show link to create/view merge request when pushing from the command line'
       end
 
       params :optional_params do
@@ -218,6 +222,7 @@ module API
             :only_allow_merge_if_all_discussions_are_resolved,
             :only_allow_merge_if_pipeline_succeeds,
             :path,
+            :printing_merge_request_link_enabled,
             :public_builds,
             :request_access_enabled,
             :shared_runners_enabled,
@@ -394,7 +399,7 @@ module API
         use :pagination
       end
       get ':id/users' do
-        users = user_project.team.users
+        users = DeclarativePolicy.subject_scope { user_project.team.users }
         users = users.search(params[:search]) if params[:search].present?
 
         present paginate(users), with: Entities::UserBasic
diff --git a/lib/api/scope.rb b/lib/api/scope.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d5165b2e482c5921b55ce8c830c99b90f19593dd
--- /dev/null
+++ b/lib/api/scope.rb
@@ -0,0 +1,23 @@
+# Encapsulate a scope used for authorization, such as `api`, or `read_user`
+module API
+  class Scope
+    attr_reader :name, :if
+
+    def initialize(name, options = {})
+      @name = name.to_sym
+      @if = options[:if]
+    end
+
+    # Are the `scopes` passed in sufficient to adequately authorize the passed
+    # request for the scope represented by the current instance of this class?
+    def sufficient?(scopes, request)
+      scopes.include?(self.name) && verify_if_condition(request)
+    end
+
+    private
+
+    def verify_if_condition(request)
+      self.if.nil? || self.if.call(request)
+    end
+  end
+end
diff --git a/lib/api/services.rb b/lib/api/services.rb
index 47bd9940f77181368bffcff147b23f7fdb96f1da..7488f95a9b7ed39d126fa8a97300f520227f6126 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -685,7 +685,7 @@ module API
 
     trigger_services.each do |service_slug, settings|
       helpers do
-        def chat_command_service(project, service_slug, params)
+        def slash_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
@@ -710,7 +710,7 @@ module API
           # This is not accurate, but done to prevent leakage of the project names
           not_found!('Service') unless project
 
-          service = chat_command_service(project, service_slug, params)
+          service = slash_command_service(project, service_slug, params)
           result = service.try(:trigger, params)
 
           if result
diff --git a/lib/api/tags.rb b/lib/api/tags.rb
index c7b1efe0bfa988d37844a6ef82dd62297c697268..633a858f8c73f17c2f4b9ac779af286653830ebd 100644
--- a/lib/api/tags.rb
+++ b/lib/api/tags.rb
@@ -44,8 +44,8 @@ module API
       post ':id/repository/tags' do
         authorize_push_project
 
-        result = ::Tags::CreateService.new(user_project, current_user).
-          execute(params[:tag_name], params[:ref], params[:message], params[:release_description])
+        result = ::Tags::CreateService.new(user_project, current_user)
+          .execute(params[:tag_name], params[:ref], params[:message], params[:release_description])
 
         if result[:status] == :success
           present result[:tag],
@@ -63,8 +63,8 @@ module API
       delete ":id/repository/tags/:tag_name", requirements: { tag_name: /.+/ } do
         authorize_push_project
 
-        result = ::Tags::DestroyService.new(user_project, current_user).
-          execute(params[:tag_name])
+        result = ::Tags::DestroyService.new(user_project, current_user)
+          .execute(params[:tag_name])
 
         if result[:status] != :success
           render_api_error!(result[:message], result[:return_code])
@@ -81,8 +81,8 @@ module API
       post ':id/repository/tags/:tag_name/release', requirements: { tag_name: /.+/ } do
         authorize_push_project
 
-        result = CreateReleaseService.new(user_project, current_user).
-          execute(params[:tag_name], params[:description])
+        result = CreateReleaseService.new(user_project, current_user)
+          .execute(params[:tag_name], params[:description])
 
         if result[:status] == :success
           present result[:release], with: Entities::Release
@@ -101,8 +101,8 @@ module API
       put ':id/repository/tags/:tag_name/release', requirements: { tag_name: /.+/ } do
         authorize_push_project
 
-        result = UpdateReleaseService.new(user_project, current_user).
-          execute(params[:tag_name], params[:description])
+        result = UpdateReleaseService.new(user_project, current_user)
+          .execute(params[:tag_name], params[:description])
 
         if result[:status] == :success
           present result[:release], with: Entities::Release
diff --git a/lib/api/users.rb b/lib/api/users.rb
index dda64715ee1edcf700941603ebeb283c786d5ed8..88bca235692f66b10638ec0c6cf15e4057515c72 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -1,13 +1,15 @@
 module API
   class Users < Grape::API
     include PaginationParams
+    include APIGuard
 
-    before do
-      allow_access_with_scope :read_user if request.get?
-      authenticate!
-    end
+    allow_access_with_scope :read_user, if: -> (request) { request.get? }
 
     resource :users, requirements: { uid: /[0-9]*/, id: /[0-9]*/ } do
+      before do
+        authenticate_non_get!
+      end
+
       helpers do
         def find_user(params)
           id = params[:user_id] || params[:id]
@@ -29,6 +31,7 @@ module API
           optional :can_create_group, type: Boolean, desc: 'Flag indicating the user can create groups'
           optional :skip_confirmation, type: Boolean, default: false, desc: 'Flag indicating the account is confirmed'
           optional :external, type: Boolean, desc: 'Flag indicating the user is an external user'
+          optional :avatar, type: File, desc: 'Avatar image for user'
           all_or_none_of :extern_uid, :provider
         end
       end
@@ -50,15 +53,22 @@ module API
         use :pagination
       end
       get do
-        unless can?(current_user, :read_users_list)
-          render_api_error!("Not authorized.", 403)
-        end
-
         authenticated_as_admin! if params[:external].present? || (params[:extern_uid].present? && params[:provider].present?)
 
         users = UsersFinder.new(current_user, params).execute
 
-        entity = current_user.admin? ? Entities::UserPublic : Entities::UserBasic
+        authorized = can?(current_user, :read_users_list)
+
+        # When `current_user` is not present, require that the `username`
+        # parameter is passed, to prevent an unauthenticated user from accessing
+        # a list of all the users on the GitLab instance. `UsersFinder` performs
+        # an exact match on the `username` parameter, so we are guaranteed to
+        # get either 0 or 1 `users` here.
+        authorized &&= params[:username].present? if current_user.blank?
+
+        forbidden!("Not authorized to access /api/v4/users") unless authorized
+
+        entity = current_user&.admin? ? Entities::UserWithAdmin : Entities::UserBasic
         present paginate(users), with: entity
       end
 
@@ -97,18 +107,18 @@ module API
         authenticated_as_admin!
 
         params = declared_params(include_missing: false)
-        user = ::Users::CreateService.new(current_user, params).execute
+        user = ::Users::CreateService.new(current_user, params).execute(skip_authorization: true)
 
         if user.persisted?
           present user, with: Entities::UserPublic
         else
-          conflict!('Email has already been taken') if User.
-              where(email: user.email).
-              count > 0
+          conflict!('Email has already been taken') if User
+              .where(email: user.email)
+              .count > 0
 
-          conflict!('Username has already been taken') if User.
-              where(username: user.username).
-              count > 0
+          conflict!('Username has already been taken') if User
+              .where(username: user.username)
+              .count > 0
 
           render_validation_error!(user)
         end
@@ -132,12 +142,12 @@ module API
         not_found!('User') unless user
 
         conflict!('Email has already been taken') if params[:email] &&
-            User.where(email: params[:email]).
-                where.not(id: user.id).count > 0
+            User.where(email: params[:email])
+                .where.not(id: user.id).count > 0
 
         conflict!('Username has already been taken') if params[:username] &&
-            User.where(username: params[:username]).
-                where.not(id: user.id).count > 0
+            User.where(username: params[:username])
+                .where.not(id: user.id).count > 0
 
         user_params = declared_params(include_missing: false)
         identity_attrs = user_params.slice(:provider, :extern_uid)
@@ -155,7 +165,9 @@ module API
 
         user_params[:password_expires_at] = Time.now if user_params[:password].present?
 
-        if user.update_attributes(user_params.except(:extern_uid, :provider))
+        result = ::Users::UpdateService.new(user, user_params.except(:extern_uid, :provider)).execute
+
+        if result[:status] == :success
           present user, with: Entities::UserPublic
         else
           render_validation_error!(user)
@@ -233,9 +245,9 @@ module API
         user = User.find_by(id: params.delete(:id))
         not_found!('User') unless user
 
-        email = user.emails.new(declared_params(include_missing: false))
+        email = Emails::CreateService.new(user, declared_params(include_missing: false)).execute
 
-        if email.save
+        if email.errors.blank?
           NotificationService.new.new_email(email)
           present email, with: Entities::Email
         else
@@ -273,8 +285,7 @@ module API
         email = user.emails.find_by(id: params[:email_id])
         not_found!('Email') unless email
 
-        email.destroy
-        user.update_secondary_emails!
+        Emails::DestroyService.new(user, email: email.email).execute
       end
 
       desc 'Delete a user. Available only for admins.' do
@@ -396,6 +407,10 @@ module API
     end
 
     resource :user do
+      before do
+        authenticate!
+      end
+
       desc 'Get the currently authenticated user' do
         success Entities::UserPublic
       end
@@ -486,9 +501,9 @@ module API
         requires :email, type: String, desc: 'The new email'
       end
       post "emails" do
-        email = current_user.emails.new(declared_params)
+        email = Emails::CreateService.new(current_user, declared_params).execute
 
-        if email.save
+        if email.errors.blank?
           NotificationService.new.new_email(email)
           present email, with: Entities::Email
         else
@@ -504,8 +519,7 @@ module API
         email = current_user.emails.find_by(id: params[:email_id])
         not_found!('Email') unless email
 
-        email.destroy
-        current_user.update_secondary_emails!
+        Emails::DestroyService.new(current_user, email: email.email).execute
       end
 
       desc 'Get a list of user activities'
@@ -516,9 +530,9 @@ module API
       get "activities" do
         authenticated_as_admin!
 
-        activities = User.
-          where(User.arel_table[:last_activity_on].gteq(params[:from])).
-          reorder(last_activity_on: :asc)
+        activities = User
+          .where(User.arel_table[:last_activity_on].gteq(params[:from]))
+          .reorder(last_activity_on: :asc)
 
         present paginate(activities), with: Entities::UserActivity
       end
diff --git a/lib/api/v3/branches.rb b/lib/api/v3/branches.rb
index 0a877b960f6684bfcf9a1e23c14f88ccd2817d1d..81b132498921113978a1c4d7ea20db7d43239d46 100644
--- a/lib/api/v3/branches.rb
+++ b/lib/api/v3/branches.rb
@@ -26,8 +26,8 @@ module API
         delete ":id/repository/branches/:branch", requirements: { branch: /.+/ } do
           authorize_push_project
 
-          result = DeleteBranchService.new(user_project, current_user).
-                   execute(params[:branch])
+          result = DeleteBranchService.new(user_project, current_user)
+                   .execute(params[:branch])
 
           if result[:status] == :success
             status(200)
@@ -55,8 +55,8 @@ module API
         end
         post ":id/repository/branches" do
           authorize_push_project
-          result = CreateBranchService.new(user_project, current_user).
-            execute(params[:branch_name], params[:ref])
+          result = CreateBranchService.new(user_project, current_user)
+            .execute(params[:branch_name], params[:ref])
 
           if result[:status] == :success
             present result[:branch],
diff --git a/lib/api/v3/entities.rb b/lib/api/v3/entities.rb
index 7c5065dee90fd2fd6e55eebbc3020bd3117ab2f8..c848f52723bd2d893d7d053f0e744aa3dc84d8df 100644
--- a/lib/api/v3/entities.rb
+++ b/lib/api/v3/entities.rb
@@ -245,9 +245,9 @@ module API
         expose :job_events, as: :build_events
         # Expose serialized properties
         expose :properties do |service, options|
-          field_names = service.fields.
-            select { |field| options[:include_passwords] || field[:type] != 'password' }.
-            map { |field| field[:name] }
+          field_names = service.fields
+            .select { |field| options[:include_passwords] || field[:type] != 'password' }
+            .map { |field| field[:name] }
           service.properties.slice(*field_names)
         end
       end
diff --git a/lib/api/v3/helpers.rb b/lib/api/v3/helpers.rb
index d9e76560d03b15463dbf7251449c92b4129cd275..4e63aa01c1a355c7434e99d227a5e42028ba4786 100644
--- a/lib/api/v3/helpers.rb
+++ b/lib/api/v3/helpers.rb
@@ -38,7 +38,10 @@ module API
           projects = projects.where(visibility_level: Gitlab::VisibilityLevel.level_value(params[:visibility]))
         end
 
-        projects = projects.where(archived: params[:archived])
+        unless params[:archived].nil?
+          projects = projects.where(archived: to_boolean(params[:archived]))
+        end
+
         projects.reorder(params[:order_by] => params[:sort])
       end
     end
diff --git a/lib/api/v3/notes.rb b/lib/api/v3/notes.rb
index 009ec5c6bbdb6026c7f1bc40909499a8a03e2a4b..23fe95e42e49113f673d7fde4dfcd63367d7c0b6 100644
--- a/lib/api/v3/notes.rb
+++ b/lib/api/v3/notes.rb
@@ -34,8 +34,8 @@ module API
                 # paginate() only works with a relation. This could lead to a
                 # mismatch between the pagination headers info and the actual notes
                 # array returned, but this is really a edge-case.
-                paginate(noteable.notes).
-                reject { |n| n.cross_reference_not_visible_for?(current_user) }
+                paginate(noteable.notes)
+                .reject { |n| n.cross_reference_not_visible_for?(current_user) }
               present notes, with: ::API::V3::Entities::Note
             else
               not_found!("Notes")
diff --git a/lib/api/v3/projects.rb b/lib/api/v3/projects.rb
index 20976b9dd08c83910fd2ee43573a5ead727b4ab9..eb090453b48ab0852e88cc675deae79941397e1b 100644
--- a/lib/api/v3/projects.rb
+++ b/lib/api/v3/projects.rb
@@ -69,7 +69,7 @@ module API
           end
 
           params :filter_params do
-            optional :archived, type: Boolean, default: false, desc: 'Limit by archived status'
+            optional :archived, type: Boolean, default: nil, desc: 'Limit by archived status'
             optional :visibility, type: String, values: %w[public internal private],
                                   desc: 'Limit by visibility'
             optional :search, type: String, desc: 'Return list of authorized projects matching the search criteria'
diff --git a/lib/api/v3/services.rb b/lib/api/v3/services.rb
index 118c6df6549d37027c84e9afc44b2007a2d1c632..2d13d6fabfd9937ea9edc352910239b9529345b4 100644
--- a/lib/api/v3/services.rb
+++ b/lib/api/v3/services.rb
@@ -608,7 +608,7 @@ module API
 
       trigger_services.each do |service_slug, settings|
         helpers do
-          def chat_command_service(project, service_slug, params)
+          def slash_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
@@ -633,7 +633,7 @@ module API
             # This is not accurate, but done to prevent leakage of the project names
             not_found!('Service') unless project
 
-            service = chat_command_service(project, service_slug, params)
+            service = slash_command_service(project, service_slug, params)
             result = service.try(:trigger, params)
 
             if result
diff --git a/lib/api/v3/tags.rb b/lib/api/v3/tags.rb
index c2541de2f5034faec75232c9d4509df148ac1041..7e5875cd030332f62ee549d01ab027e9e806ee76 100644
--- a/lib/api/v3/tags.rb
+++ b/lib/api/v3/tags.rb
@@ -22,8 +22,8 @@ module API
         delete ":id/repository/tags/:tag_name", requirements: { tag_name: /.+/ } do
           authorize_push_project
 
-          result = ::Tags::DestroyService.new(user_project, current_user).
-            execute(params[:tag_name])
+          result = ::Tags::DestroyService.new(user_project, current_user)
+            .execute(params[:tag_name])
 
           if result[:status] == :success
             status(200)
diff --git a/lib/api/v3/users.rb b/lib/api/v3/users.rb
index f4cda3b2ebacb70115bebae5fb55ec8fe6389410..cf106f2552d8edc123bab908aa310cdf5c9826ee 100644
--- a/lib/api/v3/users.rb
+++ b/lib/api/v3/users.rb
@@ -2,9 +2,11 @@ module API
   module V3
     class Users < Grape::API
       include PaginationParams
+      include APIGuard
+
+      allow_access_with_scope :read_user, if: -> (request) { request.get? }
 
       before do
-        allow_access_with_scope :read_user if request.get?
         authenticate!
       end
 
@@ -50,13 +52,13 @@ module API
           if user.persisted?
             present user, with: ::API::Entities::UserPublic
           else
-            conflict!('Email has already been taken') if User.
-                where(email: user.email).
-                count > 0
+            conflict!('Email has already been taken') if User
+                .where(email: user.email)
+                .count > 0
 
-            conflict!('Username has already been taken') if User.
-                where(username: user.username).
-                count > 0
+            conflict!('Username has already been taken') if User
+                .where(username: user.username)
+                .count > 0
 
             render_validation_error!(user)
           end
@@ -137,11 +139,11 @@ module API
           user = User.find_by(id: params[:id])
           not_found!('User') unless user
 
-          events = user.events.
-            merge(ProjectsFinder.new(current_user: current_user).execute).
-            references(:project).
-            with_associations.
-            recent
+          events = user.events
+            .merge(ProjectsFinder.new(current_user: current_user).execute)
+            .references(:project)
+            .with_associations
+            .recent
 
           present paginate(events), with: ::API::V3::Entities::Event
         end
diff --git a/lib/api/variables.rb b/lib/api/variables.rb
index 381c4ef50b06bf808d8f19bdedcfbfc3d64d0f80..10374995497bea840a02ec09f7a843ffb7d2ae8b 100644
--- a/lib/api/variables.rb
+++ b/lib/api/variables.rb
@@ -45,7 +45,7 @@ module API
         optional :protected, type: String, desc: 'Whether the variable is protected'
       end
       post ':id/variables' do
-        variable = user_project.variables.create(declared(params, include_parent_namespaces: false).to_h)
+        variable = user_project.variables.create(declared_params(include_missing: false))
 
         if variable.valid?
           present variable, with: Entities::Variable
diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb
index 8bc2dd18bdadd2ae37c93fbbdedf1cedca6f3089..7a262dd025ce0555351fe5cc44166536f1c8d418 100644
--- a/lib/banzai/filter/abstract_reference_filter.rb
+++ b/lib/banzai/filter/abstract_reference_filter.rb
@@ -216,12 +216,7 @@ module Banzai
         @references_per_project ||= begin
           refs = Hash.new { |hash, key| hash[key] = Set.new }
 
-          regex =
-            if uses_reference_pattern?
-              Regexp.union(object_class.reference_pattern, object_class.link_reference_pattern)
-            else
-              object_class.link_reference_pattern
-            end
+          regex = Regexp.union(object_class.reference_pattern, object_class.link_reference_pattern)
 
           nodes.each do |node|
             node.to_html.scan(regex) do
@@ -323,14 +318,6 @@ module Banzai
           value
         end
       end
-
-      # There might be special cases like filters
-      # that should ignore reference pattern
-      # eg: IssueReferenceFilter when using a external issues tracker
-      # In those cases this method should be overridden on the filter subclass
-      def uses_reference_pattern?
-        true
-      end
     end
   end
 end
diff --git a/lib/banzai/filter/commit_range_reference_filter.rb b/lib/banzai/filter/commit_range_reference_filter.rb
index eaacb9591b1a12cd1473ca1533fda3092d940e9e..21bcb1c5ca8555d8e241c3c9b7653b452fac4dea 100644
--- a/lib/banzai/filter/commit_range_reference_filter.rb
+++ b/lib/banzai/filter/commit_range_reference_filter.rb
@@ -30,7 +30,7 @@ module Banzai
 
       def url_for_object(range, project)
         h = Gitlab::Routing.url_helpers
-        h.namespace_project_compare_url(project.namespace, project,
+        h.project_compare_url(project,
                                         range.to_param.merge(only_path: context[:only_path]))
       end
 
diff --git a/lib/banzai/filter/commit_reference_filter.rb b/lib/banzai/filter/commit_reference_filter.rb
index 69c06117edadc83953240a98ce5aeafe7b4110f3..714e03190258b0e836c2cd643a86e39b0e7e3fac 100644
--- a/lib/banzai/filter/commit_reference_filter.rb
+++ b/lib/banzai/filter/commit_reference_filter.rb
@@ -24,7 +24,7 @@ module Banzai
 
       def url_for_object(commit, project)
         h = Gitlab::Routing.url_helpers
-        h.namespace_project_commit_url(project.namespace, project, commit,
+        h.project_commit_url(project, commit,
                                         only_path: context[:only_path])
       end
 
diff --git a/lib/banzai/filter/external_issue_reference_filter.rb b/lib/banzai/filter/external_issue_reference_filter.rb
index dce4de3ceaf800acb5dc6d1237f8646796046d70..53a229256a5f0ac5aeb5f1b5c39cc637ef3b974a 100644
--- a/lib/banzai/filter/external_issue_reference_filter.rb
+++ b/lib/banzai/filter/external_issue_reference_filter.rb
@@ -3,6 +3,8 @@ module Banzai
     # HTML filter that replaces external issue tracker references with links.
     # References are ignored if the project doesn't use an external issue
     # tracker.
+    #
+    # This filter does not support cross-project references.
     class ExternalIssueReferenceFilter < ReferenceFilter
       self.reference_type = :external_issue
 
@@ -87,7 +89,7 @@ module Banzai
       end
 
       def issue_reference_pattern
-        external_issues_cached(:issue_reference_pattern)
+        external_issues_cached(:external_issue_reference_pattern)
       end
 
       private
diff --git a/lib/banzai/filter/issue_reference_filter.rb b/lib/banzai/filter/issue_reference_filter.rb
index 044d18ff8241ef951a72af7f4253733f68c8f447..ba1a5ac84b31c98c2da1529b9df10b8e312857d9 100644
--- a/lib/banzai/filter/issue_reference_filter.rb
+++ b/lib/banzai/filter/issue_reference_filter.rb
@@ -15,10 +15,6 @@ module Banzai
         Issue
       end
 
-      def uses_reference_pattern?
-        context[:project].default_issues_tracker?
-      end
-
       def find_object(project, iid)
         issues_per_project[project][iid]
       end
@@ -38,13 +34,7 @@ module Banzai
 
           projects_per_reference.each do |path, project|
             issue_ids = references_per_project[path]
-
-            issues =
-              if project.default_issues_tracker?
-                project.issues.where(iid: issue_ids.to_a)
-              else
-                issue_ids.map { |id| ExternalIssue.new(id, project) }
-              end
+            issues = project.issues.where(iid: issue_ids.to_a)
 
             issues.each do |issue|
               hash[project][issue.iid.to_i] = issue
@@ -55,26 +45,6 @@ module Banzai
         end
       end
 
-      def object_link_title(object)
-        if object.is_a?(ExternalIssue)
-          "Issue in #{object.project.external_issue_tracker.title}"
-        else
-          super
-        end
-      end
-
-      def data_attributes_for(text, project, object, link: false)
-        if object.is_a?(ExternalIssue)
-          data_attribute(
-            project: project.id,
-            external_issue: object.id,
-            reference_type: ExternalIssueReferenceFilter.reference_type
-          )
-        else
-          super
-        end
-      end
-
       def projects_relation_for_paths(paths)
         super(paths).includes(:gitlab_issue_tracker_service)
       end
diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb
index a605dea149e56cf0d3e2fa503abf2986e2a9d58d..5364984c9d31c0b294045c5a2e21a790b8f8d4ff 100644
--- a/lib/banzai/filter/label_reference_filter.rb
+++ b/lib/banzai/filter/label_reference_filter.rb
@@ -61,8 +61,7 @@ module Banzai
 
       def url_for_object(label, project)
         h = Gitlab::Routing.url_helpers
-        h.namespace_project_issues_url(project.namespace, project, label_name: label.name,
-                                                                   only_path:  context[:only_path])
+        h.project_issues_url(project, label_name: label.name, only_path: context[:only_path])
       end
 
       def object_link_text(object, matches)
diff --git a/lib/banzai/filter/merge_request_reference_filter.rb b/lib/banzai/filter/merge_request_reference_filter.rb
index 3888acf935e5d87159daa93fc216813f80092f56..0eab865ac04802340b932a4c2a02a6379e6e781a 100644
--- a/lib/banzai/filter/merge_request_reference_filter.rb
+++ b/lib/banzai/filter/merge_request_reference_filter.rb
@@ -17,7 +17,7 @@ module Banzai
 
       def url_for_object(mr, project)
         h = Gitlab::Routing.url_helpers
-        h.namespace_project_merge_request_url(project.namespace, project, mr,
+        h.project_merge_request_url(project, mr,
                                             only_path: context[:only_path])
       end
 
diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb
index f12014e191fd204248fde3f7c6f1f2351a3c77c1..45c033d32a8de734974726f6c36c9c60a5da6f1d 100644
--- a/lib/banzai/filter/milestone_reference_filter.rb
+++ b/lib/banzai/filter/milestone_reference_filter.rb
@@ -49,7 +49,7 @@ module Banzai
 
       def url_for_object(milestone, project)
         h = Gitlab::Routing.url_helpers
-        h.namespace_project_milestone_url(project.namespace, project, milestone,
+        h.project_milestone_url(project, milestone,
                                         only_path: context[:only_path])
       end
 
diff --git a/lib/banzai/filter/snippet_reference_filter.rb b/lib/banzai/filter/snippet_reference_filter.rb
index 212a0bbf2a03f775e01509a9707f5440d7d9e5d5..134a192c22bcecd03bdc48eb2feb0c7aa74d513e 100644
--- a/lib/banzai/filter/snippet_reference_filter.rb
+++ b/lib/banzai/filter/snippet_reference_filter.rb
@@ -17,7 +17,7 @@ module Banzai
 
       def url_for_object(snippet, project)
         h = Gitlab::Routing.url_helpers
-        h.namespace_project_snippet_url(project.namespace, project, snippet,
+        h.project_snippet_url(project, snippet,
                                         only_path: context[:only_path])
       end
     end
diff --git a/lib/banzai/filter/user_reference_filter.rb b/lib/banzai/filter/user_reference_filter.rb
index a798927823fb907ad5a6dcf5fd27611475e2e0ae..f3356d6c51efe5e1391c1ba6d955550a2ee19bae 100644
--- a/lib/banzai/filter/user_reference_filter.rb
+++ b/lib/banzai/filter/user_reference_filter.rb
@@ -107,7 +107,7 @@ module Banzai
         if author && !project.team.member?(author)
           link_content
         else
-          url = urls.namespace_project_url(project.namespace, project,
+          url = urls.project_url(project,
                                            only_path: context[:only_path])
 
           data = data_attribute(project: project.id, author: author.try(:id))
diff --git a/lib/banzai/reference_extractor.rb b/lib/banzai/reference_extractor.rb
index 8e3b0c4db79361a84255bf9ee315cd82c7734a73..7e6357f8a008923224d11973b1ee05fd46085d9e 100644
--- a/lib/banzai/reference_extractor.rb
+++ b/lib/banzai/reference_extractor.rb
@@ -10,8 +10,8 @@ module Banzai
     end
 
     def references(type, project, current_user = nil)
-      processor = Banzai::ReferenceParser[type].
-        new(project, current_user)
+      processor = Banzai::ReferenceParser[type]
+        .new(project, current_user)
 
       processor.process(html_documents)
     end
diff --git a/lib/banzai/reference_parser/issue_parser.rb b/lib/banzai/reference_parser/issue_parser.rb
index 89ec715ddf6d91afbca086e4fa3f81f37da87a81..a65bbe23958c3808bd19923eace715ba72934f6d 100644
--- a/lib/banzai/reference_parser/issue_parser.rb
+++ b/lib/banzai/reference_parser/issue_parser.rb
@@ -4,13 +4,10 @@ module Banzai
       self.reference_type = :issue
 
       def nodes_visible_to_user(user, nodes)
-        # It is not possible to check access rights for external issue trackers
-        return nodes if project && project.external_issue_tracker
-
         issues = issues_for_nodes(nodes)
 
-        readable_issues = Ability.
-          issues_readable_by_user(issues.values, user).to_set
+        readable_issues = Ability
+          .issues_readable_by_user(issues.values, user).to_set
 
         nodes.select do |node|
           readable_issues.include?(issues[node])
diff --git a/lib/banzai/reference_parser/user_parser.rb b/lib/banzai/reference_parser/user_parser.rb
index 3efbd2fd631dd53fa20a1ae0b35d9755b6010f2c..4d336068861df64f9f241e11e4eccce66736c494 100644
--- a/lib/banzai/reference_parser/user_parser.rb
+++ b/lib/banzai/reference_parser/user_parser.rb
@@ -99,8 +99,8 @@ module Banzai
       def find_users_for_projects(ids)
         return [] if ids.empty?
 
-        collection_objects_for_ids(Project, ids).
-          flat_map { |p| p.team.members.to_a }
+        collection_objects_for_ids(Project, ids)
+          .flat_map { |p| p.team.members.to_a }
       end
 
       def can_read_reference?(user, ref_project, node)
diff --git a/lib/ci/api/helpers.rb b/lib/ci/api/helpers.rb
index 5109dc9670fcd7779ee1e8d7660692ca2c22b8f8..a40b6ab6c9f5836cdfeef01dd633b4427c1f87f9 100644
--- a/lib/ci/api/helpers.rb
+++ b/lib/ci/api/helpers.rb
@@ -28,7 +28,8 @@ module Ci
 
         yield if block_given?
 
-        forbidden!('Project has been deleted!') unless build.project
+        project = build.project
+        forbidden!('Project has been deleted!') if project.nil? || project.pending_delete?
         forbidden!('Build has been erased!') if build.erased?
       end
 
diff --git a/lib/ci/charts.rb b/lib/ci/charts.rb
index 3decc3b1a269e609064157e46adec6c1281a4fcf..872e418c788dfa16e073f833629dea09825c14c8 100644
--- a/lib/ci/charts.rb
+++ b/lib/ci/charts.rb
@@ -2,10 +2,10 @@ module Ci
   module Charts
     module DailyInterval
       def grouped_count(query)
-        query.
-          group("DATE(#{Ci::Build.table_name}.created_at)").
-          count(:created_at).
-          transform_keys { |date| date.strftime(@format) }
+        query
+          .group("DATE(#{Ci::Pipeline.table_name}.created_at)")
+          .count(:created_at)
+          .transform_keys { |date| date.strftime(@format) }
       end
 
       def interval_step
@@ -16,14 +16,14 @@ module Ci
     module MonthlyInterval
       def grouped_count(query)
         if Gitlab::Database.postgresql?
-          query.
-            group("to_char(#{Ci::Build.table_name}.created_at, '01 Month YYYY')").
-            count(:created_at).
-            transform_keys(&:squish)
+          query
+            .group("to_char(#{Ci::Pipeline.table_name}.created_at, '01 Month YYYY')")
+            .count(:created_at)
+            .transform_keys(&:squish)
         else
-          query.
-            group("DATE_FORMAT(#{Ci::Build.table_name}.created_at, '01 %M %Y')").
-            count(:created_at)
+          query
+            .group("DATE_FORMAT(#{Ci::Pipeline.table_name}.created_at, '01 %M %Y')")
+            .count(:created_at)
         end
       end
 
@@ -33,21 +33,21 @@ module Ci
     end
 
     class Chart
-      attr_reader :labels, :total, :success, :project, :build_times
+      attr_reader :labels, :total, :success, :project, :pipeline_times
 
       def initialize(project)
         @labels = []
         @total = []
         @success = []
-        @build_times = []
+        @pipeline_times = []
         @project = project
 
         collect
       end
 
       def collect
-        query = project.builds.
-          where("? > #{Ci::Build.table_name}.created_at AND #{Ci::Build.table_name}.created_at > ?", @to, @from)
+        query = project.pipelines
+          .where("? > #{Ci::Pipeline.table_name}.created_at AND #{Ci::Pipeline.table_name}.created_at > ?", @to, @from)
 
         totals_count  = grouped_count(query)
         success_count = grouped_count(query.success)
@@ -101,14 +101,14 @@ module Ci
       end
     end
 
-    class BuildTime < Chart
+    class PipelineTime < Chart
       def collect
         commits = project.pipelines.last(30)
 
         commits.each do |commit|
           @labels << commit.short_sha
           duration = commit.duration || 0
-          @build_times << (duration / 60)
+          @pipeline_times << (duration / 60)
         end
       end
     end
diff --git a/lib/declarative_policy.rb b/lib/declarative_policy.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d9959bc1aff47dc86c22357749b4fa139efc4d83
--- /dev/null
+++ b/lib/declarative_policy.rb
@@ -0,0 +1,58 @@
+require_dependency 'declarative_policy/cache'
+require_dependency 'declarative_policy/condition'
+require_dependency 'declarative_policy/dsl'
+require_dependency 'declarative_policy/preferred_scope'
+require_dependency 'declarative_policy/rule'
+require_dependency 'declarative_policy/runner'
+require_dependency 'declarative_policy/step'
+
+require_dependency 'declarative_policy/base'
+
+module DeclarativePolicy
+  class << self
+    def policy_for(user, subject, opts = {})
+      cache = opts[:cache] || {}
+      key = Cache.policy_key(user, subject)
+
+      cache[key] ||= class_for(subject).new(user, subject, opts)
+    end
+
+    def class_for(subject)
+      return GlobalPolicy if subject == :global
+      return NilPolicy if subject.nil?
+
+      subject = find_delegate(subject)
+
+      subject.class.ancestors.each do |klass|
+        next unless klass.name
+
+        begin
+          policy_class = "#{klass.name}Policy".constantize
+
+          # NOTE: the < operator here tests whether policy_class
+          # inherits from Base. We can't use #is_a? because that
+          # tests for *instances*, not *subclasses*.
+          return policy_class if policy_class < Base
+        rescue NameError
+          nil
+        end
+      end
+
+      raise "no policy for #{subject.class.name}"
+    end
+
+    private
+
+    def find_delegate(subject)
+      seen = Set.new
+
+      while subject.respond_to?(:declarative_policy_delegate)
+        raise ArgumentError, "circular delegations" if seen.include?(subject.object_id)
+        seen << subject.object_id
+        subject = subject.declarative_policy_delegate
+      end
+
+      subject
+    end
+  end
+end
diff --git a/lib/declarative_policy/base.rb b/lib/declarative_policy/base.rb
new file mode 100644
index 0000000000000000000000000000000000000000..df94cafb6a1424eaa4f90d7ac130db79358afe37
--- /dev/null
+++ b/lib/declarative_policy/base.rb
@@ -0,0 +1,329 @@
+module DeclarativePolicy
+  class Base
+    # A map of ability => list of rules together with :enable
+    # or :prevent actions. Used to look up which rules apply to
+    # a given ability. See Base.ability_map
+    class AbilityMap
+      attr_reader :map
+      def initialize(map = {})
+        @map = map
+      end
+
+      # This merge behavior is different than regular hashes - if both
+      # share a key, the values at that key are concatenated, rather than
+      # overridden.
+      def merge(other)
+        conflict_proc = proc { |key, my_val, other_val| my_val + other_val }
+        AbilityMap.new(@map.merge(other.map, &conflict_proc))
+      end
+
+      def actions(key)
+        @map[key] ||= []
+      end
+
+      def enable(key, rule)
+        actions(key) << [:enable, rule]
+      end
+
+      def prevent(key, rule)
+        actions(key) << [:prevent, rule]
+      end
+    end
+
+    class << self
+      # The `own_ability_map` vs `ability_map` distinction is used so that
+      # the data structure is properly inherited - with subclasses recursively
+      # merging their parent class.
+      #
+      # This pattern is also used for conditions, global_actions, and delegations.
+      def ability_map
+        if self == Base
+          own_ability_map
+        else
+          superclass.ability_map.merge(own_ability_map)
+        end
+      end
+
+      def own_ability_map
+        @own_ability_map ||= AbilityMap.new
+      end
+
+      # an inheritable map of conditions, by name
+      def conditions
+        if self == Base
+          own_conditions
+        else
+          superclass.conditions.merge(own_conditions)
+        end
+      end
+
+      def own_conditions
+        @own_conditions ||= {}
+      end
+
+      # a list of global actions, generated by `prevent_all`. these aren't
+      # stored in `ability_map` because they aren't indexed by a particular
+      # ability.
+      def global_actions
+        if self == Base
+          own_global_actions
+        else
+          superclass.global_actions + own_global_actions
+        end
+      end
+
+      def own_global_actions
+        @own_global_actions ||= []
+      end
+
+      # an inheritable map of delegations, indexed by name (which may be
+      # autogenerated)
+      def delegations
+        if self == Base
+          own_delegations
+        else
+          superclass.delegations.merge(own_delegations)
+        end
+      end
+
+      def own_delegations
+        @own_delegations ||= {}
+      end
+
+      # all the [rule, action] pairs that apply to a particular ability.
+      # we combine the specific ones looked up in ability_map with the global
+      # ones.
+      def configuration_for(ability)
+        ability_map.actions(ability) + global_actions
+      end
+
+      ### declaration methods ###
+
+      def delegate(name = nil, &delegation_block)
+        if name.nil?
+          @delegate_name_counter ||= 0
+          @delegate_name_counter += 1
+          name = :"anonymous_#{@delegate_name_counter}"
+        end
+
+        name = name.to_sym
+
+        if delegation_block.nil?
+          delegation_block = proc { @subject.__send__(name) }
+        end
+
+        own_delegations[name] = delegation_block
+      end
+
+      # Declares a rule, constructed using RuleDsl, and returns
+      # a PolicyDsl which is used for registering the rule with
+      # this class. PolicyDsl will call back into Base.enable_when,
+      # Base.prevent_when, and Base.prevent_all_when.
+      def rule(&b)
+        rule = RuleDsl.new(self).instance_eval(&b)
+        PolicyDsl.new(self, rule)
+      end
+
+      # A hash in which to store calls to `desc` and `with_scope`, etc.
+      def last_options
+        @last_options ||= {}.with_indifferent_access
+      end
+
+      # retrieve and zero out the previously set options (used in .condition)
+      def last_options!
+        last_options.tap { @last_options = nil }
+      end
+
+      # Declare a description for the following condition. Currently unused,
+      # but opens the potential for explaining to users why they were or were
+      # not able to do something.
+      def desc(description)
+        last_options[:description] = description
+      end
+
+      def with_options(opts = {})
+        last_options.merge!(opts)
+      end
+
+      def with_scope(scope)
+        with_options scope: scope
+      end
+
+      def with_score(score)
+        with_options score: score
+      end
+
+      # Declares a condition. It gets stored in `own_conditions`, and generates
+      # a query method based on the condition's name.
+      def condition(name, opts = {}, &value)
+        name = name.to_sym
+
+        opts = last_options!.merge(opts)
+        opts[:context_key] ||= self.name
+
+        condition = Condition.new(name, opts, &value)
+
+        self.own_conditions[name] = condition
+
+        define_method(:"#{name}?") { condition(name).pass? }
+      end
+
+      # These next three methods are mainly called from PolicyDsl,
+      # and are responsible for "inverting" the relationship between
+      # an ability and a rule. We store in `ability_map` a map of
+      # abilities to rules that affect them, together with a
+      # symbol indicating :prevent or :enable.
+      def enable_when(abilities, rule)
+        abilities.each { |a| own_ability_map.enable(a, rule) }
+      end
+
+      def prevent_when(abilities, rule)
+        abilities.each { |a| own_ability_map.prevent(a, rule) }
+      end
+
+      # we store global prevents (from `prevent_all`) separately,
+      # so that they can be combined into every decision made.
+      def prevent_all_when(rule)
+        own_global_actions << [:prevent, rule]
+      end
+    end
+
+    # A policy object contains a specific user and subject on which
+    # to compute abilities. For this reason it's sometimes called
+    # "context" within the framework.
+    #
+    # It also stores a reference to the cache, so it can be used
+    # to cache computations by e.g. ManifestCondition.
+    attr_reader :user, :subject, :cache
+    def initialize(user, subject, opts = {})
+      @user = user
+      @subject = subject
+      @cache = opts[:cache] || {}
+    end
+
+    # helper for checking abilities on this and other subjects
+    # for the current user.
+    def can?(ability, new_subject = :_self)
+      return allowed?(ability) if new_subject == :_self
+
+      policy_for(new_subject).allowed?(ability)
+    end
+
+    # This is the main entry point for permission checks. It constructs
+    # or looks up a Runner for the given ability and asks it if it passes.
+    def allowed?(*abilities)
+      abilities.all? { |a| runner(a).pass? }
+    end
+
+    # The inverse of #allowed?, used mainly in specs.
+    def disallowed?(*abilities)
+      abilities.all? { |a| !runner(a).pass? }
+    end
+
+    # computes the given ability and prints a helpful debugging output
+    # showing which 
+    def debug(ability, *a)
+      runner(ability).debug(*a)
+    end
+
+    desc "Unknown user"
+    condition(:anonymous, scope: :user, score: 0) { @user.nil? }
+
+    desc "By default"
+    condition(:default, scope: :global, score: 0) { true }
+
+    def repr
+      subject_repr =
+        if @subject.respond_to?(:id)
+          "#{@subject.class.name}/#{@subject.id}"
+        else
+          @subject.inspect
+        end
+
+      user_repr =
+        if @user
+          @user.to_reference
+        else
+          "<anonymous>"
+        end
+
+      "(#{user_repr} : #{subject_repr})"
+    end
+
+    def inspect
+      "#<#{self.class.name} #{repr}>"
+    end
+
+    # returns a Runner for the given ability, capable of computing whether
+    # the ability is allowed. Runners are cached on the policy (which itself
+    # is cached on @cache), and caches its result. This is how we perform caching
+    # at the ability level.
+    def runner(ability)
+      ability = ability.to_sym
+      @runners ||= {}
+      @runners[ability] ||=
+        begin
+          delegated_runners = delegated_policies.values.compact.map { |p| p.runner(ability) }
+          own_runner = Runner.new(own_steps(ability))
+          delegated_runners.inject(own_runner, &:merge_runner)
+        end
+    end
+
+    # Helpers for caching. Used by ManifestCondition in performing condition
+    # computation.
+    #
+    # NOTE we can't use ||= here because the value might be the
+    # boolean `false`
+    def cache(key, &b)
+      return @cache[key] if cached?(key)
+      @cache[key] = yield
+    end
+
+    def cached?(key)
+      !@cache[key].nil?
+    end
+
+    # returns a ManifestCondition capable of computing itself. The computation
+    # will use our own @cache.
+    def condition(name)
+      name = name.to_sym
+      @_conditions ||= {}
+      @_conditions[name] ||=
+        begin
+          raise "invalid condition #{name}" unless self.class.conditions.key?(name)
+          ManifestCondition.new(self.class.conditions[name], self)
+        end
+    end
+
+    # used in specs - returns true if there is no possible way for any action
+    # to be allowed, determined only by the global :prevent_all rules.
+    def banned?
+      global_steps = self.class.global_actions.map { |(action, rule)| Step.new(self, rule, action) }
+      !Runner.new(global_steps).pass?
+    end
+
+    # A list of other policies that we've delegated to (see `Base.delegate`)
+    def delegated_policies
+      @delegated_policies ||= self.class.delegations.transform_values do |block|
+        new_subject = instance_eval(&block)
+
+        # never delegate to nil, as that would immediately prevent_all
+        next if new_subject.nil?
+
+        policy_for(new_subject)
+      end
+    end
+
+    def policy_for(other_subject)
+      DeclarativePolicy.policy_for(@user, other_subject, cache: @cache)
+    end
+
+    protected
+
+    # constructs steps that come from this policy and not from any delegations
+    def own_steps(ability)
+      rules = self.class.configuration_for(ability)
+      rules.map { |(action, rule)| Step.new(self, rule, action) }
+    end
+  end
+end
diff --git a/lib/declarative_policy/cache.rb b/lib/declarative_policy/cache.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b8cc60074c7a0330f2e3c73d6eeccbd8447f3fa8
--- /dev/null
+++ b/lib/declarative_policy/cache.rb
@@ -0,0 +1,32 @@
+module DeclarativePolicy
+  module Cache
+    class << self
+      def user_key(user)
+        return '<anonymous>' if user.nil?
+        id_for(user)
+      end
+
+      def policy_key(user, subject)
+        u = user_key(user)
+        s = subject_key(subject)
+        "/dp/policy/#{u}/#{s}"
+      end
+
+      def subject_key(subject)
+        return '<nil>' if subject.nil?
+        return subject.inspect if subject.is_a?(Symbol)
+        "#{subject.class.name}:#{id_for(subject)}"
+      end
+
+      private
+
+      def id_for(obj)
+        if obj.respond_to?(:id) && obj.id
+          obj.id.to_s
+        else
+          "##{obj.object_id}"
+        end
+      end
+    end
+  end
+end
diff --git a/lib/declarative_policy/condition.rb b/lib/declarative_policy/condition.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9d7cf6b9726a7c55a487d5ec644a66b336d0c728
--- /dev/null
+++ b/lib/declarative_policy/condition.rb
@@ -0,0 +1,102 @@
+module DeclarativePolicy
+  # A Condition is the data structure that is created by the
+  # `condition` declaration on DeclarativePolicy::Base. It is
+  # more or less just a struct of the data passed to that
+  # declaration. It holds on to the block to be instance_eval'd
+  # on a context (instance of Base) later, via #compute.
+  class Condition
+    attr_reader :name, :description, :scope
+    attr_reader :manual_score
+    attr_reader :context_key
+    def initialize(name, opts = {}, &compute)
+      @name = name
+      @compute = compute
+      @scope = opts.fetch(:scope, :normal)
+      @description = opts.delete(:description)
+      @context_key = opts[:context_key]
+      @manual_score = opts.fetch(:score, nil)
+    end
+
+    def compute(context)
+      !!context.instance_eval(&@compute)
+    end
+
+    def key
+      "#{@context_key}/#{@name}"
+    end
+  end
+
+  # In contrast to a Condition, a ManifestCondition contains
+  # a Condition and a context object, and is capable of calculating
+  # a result itself. This is the return value of Base#condition.
+  class ManifestCondition
+    def initialize(condition, context)
+      @condition = condition
+      @context = context
+    end
+
+    # The main entry point - does this condition pass? We reach into
+    # the context's cache here so that we can share in the global
+    # cache (often RequestStore or similar).
+    def pass?
+      @context.cache(cache_key) { @condition.compute(@context) }
+    end
+
+    # Whether we've already computed this condition.
+    def cached?
+      @context.cached?(cache_key)
+    end
+
+    # This is used to score Rule::Condition. See Rule::Condition#score
+    # and Runner#steps_by_score for how scores are used.
+    #
+    # The number here is intended to represent, abstractly, how
+    # expensive it would be to calculate this condition.
+    #
+    # See #cache_key for info about @condition.scope.
+    def score
+      # If we've been cached, no computation is necessary.
+      return 0 if cached?
+
+      # Use the override from condition(score: ...) if present
+      return @condition.manual_score if @condition.manual_score
+
+      # Global scope rules are cheap due to max cache sharing
+      return 2 if  @condition.scope == :global
+
+      # "Normal" rules can't share caches with any other policies
+      return 16 if @condition.scope == :normal
+
+      # otherwise, we're :user or :subject scope, so it's 4 if
+      # the caller has declared a preference
+      return 4 if @condition.scope == DeclarativePolicy.preferred_scope
+
+      # and 8 for all other :user or :subject scope conditions.
+      8
+    end
+
+    private
+
+    # This method controls the caching for the condition. This is where
+    # the condition(scope: ...) option comes into play. Notice that
+    # depending on the scope, we may cache only by the user or only by
+    # the subject, resulting in sharing across different policy objects.
+    def cache_key
+      case @condition.scope
+      when :normal  then "/dp/condition/#{@condition.key}/#{user_key},#{subject_key}"
+      when :user    then "/dp/condition/#{@condition.key}/#{user_key}"
+      when :subject then "/dp/condition/#{@condition.key}/#{subject_key}"
+      when :global  then "/dp/condition/#{@condition.key}"
+      else raise 'invalid scope'
+      end
+    end
+
+    def user_key
+      Cache.user_key(@context.user)
+    end
+
+    def subject_key
+      Cache.subject_key(@context.subject)
+    end
+  end
+end
diff --git a/lib/declarative_policy/dsl.rb b/lib/declarative_policy/dsl.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b26807a762235a8550acaaf73dcb554894ecaf82
--- /dev/null
+++ b/lib/declarative_policy/dsl.rb
@@ -0,0 +1,103 @@
+module DeclarativePolicy
+  # The DSL evaluation context inside rule { ... } blocks.
+  # Responsible for creating and combining Rule objects.
+  #
+  # See Base.rule
+  class RuleDsl
+    def initialize(context_class)
+      @context_class = context_class
+    end
+
+    def can?(ability)
+      Rule::Ability.new(ability)
+    end
+
+    def all?(*rules)
+      Rule::And.make(rules)
+    end
+
+    def any?(*rules)
+      Rule::Or.make(rules)
+    end
+
+    def none?(*rules)
+      ~Rule::Or.new(rules)
+    end
+
+    def cond(condition)
+      Rule::Condition.new(condition)
+    end
+
+    def delegate(delegate_name, condition)
+      Rule::DelegatedCondition.new(delegate_name, condition)
+    end
+
+    def method_missing(m, *a, &b)
+      return super unless a.size == 0 && !block_given?
+
+      if @context_class.delegations.key?(m)
+        DelegateDsl.new(self, m)
+      else
+        cond(m.to_sym)
+      end
+    end
+  end
+
+  # Used when the name of a delegate is mentioned in
+  # the rule DSL.
+  class DelegateDsl
+    def initialize(rule_dsl, delegate_name)
+      @rule_dsl = rule_dsl
+      @delegate_name = delegate_name
+    end
+
+    def method_missing(m, *a, &b)
+      return super unless a.size == 0 && !block_given?
+
+      @rule_dsl.delegate(@delegate_name, m)
+    end
+  end
+
+  # The return value of a rule { ... } declaration.
+  # Can call back to register rules with the containing
+  # Policy class (context_class here). See Base.rule
+  #
+  # Note that the #policy method just performs an #instance_eval,
+  # which is useful for multiple #enable or #prevent callse.
+  #
+  # Also provides a #method_missing proxy to the context
+  # class's class methods, so that helper methods can be
+  # defined and used in a #policy { ... } block.
+  class PolicyDsl
+    def initialize(context_class, rule)
+      @context_class = context_class
+      @rule = rule
+    end
+
+    def policy(&b)
+      instance_eval(&b)
+    end
+
+    def enable(*abilities)
+      @context_class.enable_when(abilities, @rule)
+    end
+
+    def prevent(*abilities)
+      @context_class.prevent_when(abilities, @rule)
+    end
+
+    def prevent_all
+      @context_class.prevent_all_when(@rule)
+    end
+
+    def method_missing(m, *a, &b)
+      return super unless @context_class.respond_to?(m)
+
+      @context_class.__send__(m, *a, &b)
+    end
+
+    def respond_to_missing?(m)
+      @context_class.respond_to?(m) || super
+    end
+  end
+end
diff --git a/lib/declarative_policy/preferred_scope.rb b/lib/declarative_policy/preferred_scope.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b07540981497f5af9c66e20eb11f8fe37bd89256
--- /dev/null
+++ b/lib/declarative_policy/preferred_scope.rb
@@ -0,0 +1,28 @@
+module DeclarativePolicy
+  PREFERRED_SCOPE_KEY = :"DeclarativePolicy.preferred_scope"
+
+  class << self
+    def with_preferred_scope(scope, &b)
+      Thread.current[PREFERRED_SCOPE_KEY], old_scope = scope, Thread.current[PREFERRED_SCOPE_KEY]
+      yield
+    ensure
+      Thread.current[PREFERRED_SCOPE_KEY] = old_scope
+    end
+
+    def preferred_scope
+      Thread.current[PREFERRED_SCOPE_KEY]
+    end
+
+    def user_scope(&b)
+      with_preferred_scope(:user, &b)
+    end
+
+    def subject_scope(&b)
+      with_preferred_scope(:subject, &b)
+    end
+
+    def preferred_scope=(scope)
+      Thread.current[PREFERRED_SCOPE_KEY] = scope
+    end
+  end
+end
diff --git a/lib/declarative_policy/rule.rb b/lib/declarative_policy/rule.rb
new file mode 100644
index 0000000000000000000000000000000000000000..bfcec2414895359540df4b74a9e827bc6e434f46
--- /dev/null
+++ b/lib/declarative_policy/rule.rb
@@ -0,0 +1,301 @@
+module DeclarativePolicy
+  module Rule
+    # A Rule is the object that results from the `rule` declaration,
+    # usually built using the DSL in `RuleDsl`. It is a basic logical
+    # combination of building blocks, and is capable of deciding,
+    # given a context (instance of DeclarativePolicy::Base) whether it
+    # passes or not. Note that this decision doesn't by itself know
+    # how that affects the actual ability decision - for that, a
+    # `Step` is used.
+    class Base
+      def self.make(*a)
+        new(*a).simplify
+      end
+
+      # true or false whether this rule passes.
+      # `context` is a policy - an instance of
+      # DeclarativePolicy::Base.
+      def pass?(context)
+        raise 'abstract'
+      end
+
+      # same as #pass? except refuses to do any I/O,
+      # returning nil if the result is not yet cached.
+      # used for accurately scoring And/Or
+      def cached_pass?(context)
+        raise 'abstract'
+      end
+
+      # abstractly, how long would it take to compute
+      # this rule? lower-scored rules are tried first.
+      def score(context)
+        raise 'abstract'
+      end
+
+      # unwrap double negatives and nested and/or
+      def simplify
+        self
+      end
+
+      # convenience combination methods
+      def or(other)
+        Or.make([self, other])
+      end
+
+      def and(other)
+        And.make([self, other])
+      end
+
+      def negate
+        Not.make(self)
+      end
+
+      alias_method :|, :or
+      alias_method :&, :and
+      alias_method :~@, :negate
+
+      def inspect
+        "#<Rule #{repr}>"
+      end
+    end
+
+    # A rule that checks a condition. This is the
+    # type of rule that results from a basic bareword
+    # in the rule dsl (see RuleDsl#method_missing).
+    class Condition < Base
+      def initialize(name)
+        @name = name
+      end
+
+      # we delegate scoring to the condition. See
+      # ManifestCondition#score.
+      def score(context)
+        context.condition(@name).score
+      end
+
+      # Let the ManifestCondition from the context
+      # decide whether we pass.
+      def pass?(context)
+        context.condition(@name).pass?
+      end
+
+      # returns nil unless it's already cached
+      def cached_pass?(context)
+        condition = context.condition(@name)
+        return nil unless condition.cached?
+        condition.pass?
+      end
+
+      def description(context)
+        context.class.conditions[@name].description
+      end
+
+      def repr
+        @name.to_s
+      end
+    end
+
+    # A rule constructed from DelegateDsl - using a condition from a
+    # delegated policy.
+    class DelegatedCondition < Base
+      # Internal use only - this is rescued each time it's raised.
+      MissingDelegate = Class.new(StandardError)
+
+      def initialize(delegate_name, name)
+        @delegate_name = delegate_name
+        @name = name
+      end
+
+      def delegated_context(context)
+        policy = context.delegated_policies[@delegate_name]
+        raise MissingDelegate if policy.nil?
+        policy
+      end
+
+      def score(context)
+        delegated_context(context).condition(@name).score
+      rescue MissingDelegate
+        0
+      end
+
+      def cached_pass?(context)
+        condition = delegated_context(context).condition(@name)
+        return nil unless condition.cached?
+        condition.pass?
+      rescue MissingDelegate
+        false
+      end
+
+      def pass?(context)
+        delegated_context(context).condition(@name).pass?
+      rescue MissingDelegate
+        false
+      end
+
+      def repr
+        "#{@delegate_name}.#{@name}"
+      end
+    end
+
+    # A rule constructed from RuleDsl#can?. Computes a different ability
+    # on the same subject.
+    class Ability < Base
+      attr_reader :ability
+      def initialize(ability)
+        @ability = ability
+      end
+
+      # We ask the ability's runner for a score
+      def score(context)
+        context.runner(@ability).score
+      end
+
+      def pass?(context)
+        context.allowed?(@ability)
+      end
+
+      def cached_pass?(context)
+        runner = context.runner(@ability)
+        return nil unless runner.cached?
+        runner.pass?
+      end
+
+      def description(context)
+        "User can #{@ability.inspect}"
+      end
+
+      def repr
+        "can?(#{@ability.inspect})"
+      end
+    end
+
+    # Logical `and`, containing a list of rules. Only passes
+    # if all of them do.
+    class And < Base
+      attr_reader :rules
+      def initialize(rules)
+        @rules = rules
+      end
+
+      def simplify
+        simplified_rules = @rules.flat_map do |rule|
+          simplified = rule.simplify
+          case simplified
+          when And then simplified.rules
+          else [simplified]
+          end
+        end
+
+        And.new(simplified_rules)
+      end
+
+      def score(context)
+        return 0 unless cached_pass?(context).nil?
+
+        # note that cached rules will have score 0 anyways.
+        @rules.map { |r| r.score(context) }.inject(0, :+)
+      end
+
+      def pass?(context)
+        # try to find a cached answer before
+        # checking in order
+        cached = cached_pass?(context)
+        return cached unless cached.nil?
+
+        @rules.all? { |r| r.pass?(context) }
+      end
+
+      def cached_pass?(context)
+        passes = @rules.map { |r| r.cached_pass?(context) }
+        return false if passes.any? { |p| p == false }
+        return true if passes.all? { |p| p == true }
+
+        nil
+      end
+
+      def repr
+        "all?(#{rules.map(&:repr).join(', ')})"
+      end
+    end
+
+    # Logical `or`. Mirrors And.
+    class Or < Base
+      attr_reader :rules
+      def initialize(rules)
+        @rules = rules
+      end
+
+      def pass?(context)
+        cached = cached_pass?(context)
+        return cached unless cached.nil?
+
+        @rules.any? { |r| r.pass?(context) }
+      end
+
+      def simplify
+        simplified_rules = @rules.flat_map do |rule|
+          simplified = rule.simplify
+          case simplified
+          when Or then simplified.rules
+          else [simplified]
+          end
+        end
+
+        Or.new(simplified_rules)
+      end
+
+      def cached_pass?(context)
+        passes = @rules.map { |r| r.cached_pass?(context) }
+        return true if passes.any? { |p| p == true }
+        return false if passes.all? { |p| p == false }
+
+        nil
+      end
+
+      def score(context)
+        return 0 unless cached_pass?(context).nil?
+        @rules.map { |r| r.score(context) }.inject(0, :+)
+      end
+
+      def repr
+        "any?(#{@rules.map(&:repr).join(', ')})"
+      end
+    end
+
+    class Not < Base
+      attr_reader :rule
+      def initialize(rule)
+        @rule = rule
+      end
+
+      def simplify
+        case @rule
+        when And then Or.new(@rule.rules.map(&:negate)).simplify
+        when Or then And.new(@rule.rules.map(&:negate)).simplify
+        when Not then @rule.rule.simplify
+        else Not.new(@rule.simplify)
+        end
+      end
+
+      def pass?(context)
+        !@rule.pass?(context)
+      end
+
+      def cached_pass?(context)
+        case @rule.cached_pass?(context)
+        when nil then nil
+        when true then false
+        when false then true
+        end
+      end
+
+      def score(context)
+        @rule.score(context)
+      end
+
+      def repr
+        "~#{@rule.repr}"
+      end
+    end
+  end
+end
diff --git a/lib/declarative_policy/runner.rb b/lib/declarative_policy/runner.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b5c615da4e3630481ff15a48278f55790a97bd12
--- /dev/null
+++ b/lib/declarative_policy/runner.rb
@@ -0,0 +1,181 @@
+module DeclarativePolicy
+  class Runner
+    class State
+      def initialize
+        @enabled = false
+        @prevented = false
+      end
+
+      def enable!
+        @enabled = true
+      end
+
+      def enabled?
+        @enabled
+      end
+
+      def prevent!
+        @prevented = true
+      end
+
+      def prevented?
+        @prevented
+      end
+
+      def pass?
+        !prevented? && enabled?
+      end
+    end
+
+    # a Runner contains a list of Steps to be run.
+    attr_reader :steps
+    def initialize(steps)
+      @steps = steps
+    end
+
+    # We make sure only to run any given Runner once,
+    # and just continue to use the resulting @state
+    # that's left behind.
+    def cached?
+      !!@state
+    end
+
+    # used by Rule::Ability. See #steps_by_score
+    def score
+      return 0 if cached?
+      steps.map(&:score).inject(0, :+)
+    end
+
+    def merge_runner(other)
+      Runner.new(@steps + other.steps)
+    end
+
+    # The main entry point, called for making an ability decision.
+    # See #run and DeclarativePolicy::Base#can?
+    def pass?
+      run unless cached?
+
+      @state.pass?
+    end
+
+    # see DeclarativePolicy::Base#debug
+    def debug(out = $stderr)
+      run(out)
+    end
+
+    private
+
+    def flatten_steps!
+      @steps = @steps.flat_map { |s| s.flattened(@steps) }
+    end
+
+    # This method implements the semantic of "one enable and no prevents".
+    # It relies on #steps_by_score for the main loop, and updates @state
+    # with the result of the step.
+    def run(debug = nil)
+      @state = State.new
+
+      steps_by_score do |step, score|
+        passed = nil
+        case step.action
+        when :enable then
+          # we only check :enable actions if they have a chance of
+          # changing the outcome - if no other rule has enabled or
+          # prevented.
+          unless @state.enabled? || @state.prevented?
+            passed = step.pass?
+            @state.enable! if passed
+          end
+
+          debug << inspect_step(step, score, passed) if debug
+        when :prevent then
+          # we only check :prevent actions if the state hasn't already
+          # been prevented.
+          unless @state.prevented?
+            passed = step.pass?
+            if passed
+              @state.prevent!
+              return unless debug
+            end
+          end
+
+          debug << inspect_step(step, score, passed) if debug
+        else raise "invalid action #{step.action.inspect}"
+        end
+      end
+
+      @state
+    end
+
+    # This is the core spot where all those `#score` methods matter.
+    # It is critcal for performance to run steps in the correct order,
+    # so that we don't compute expensive conditions (potentially n times
+    # if we're called on, say, a large list of users).
+    #
+    # In order to determine the cheapest step to run next, we rely on
+    # Step#score, which returns a numerical rating of how expensive
+    # it would be to calculate - the lower the better. It would be
+    # easy enough to statically sort by these scores, but we can do
+    # a little better - the scores are cache-aware (conditions that
+    # are already in the cache have score 0), which means that running
+    # a step can actually change the scores of other steps.
+    #
+    # So! The way we sort here involves re-scoring at every step. This
+    # is by necessity quadratic, but most of the time the number of steps
+    # will be low. But just in case, if the number of steps exceeds 50,
+    # we print a warning and fall back to a static sort.
+    #
+    # For each step, we yield the step object along with the computed score
+    # for debugging purposes.
+    def steps_by_score(&b)
+      flatten_steps!
+
+      if @steps.size > 50
+        warn "DeclarativePolicy: large number of steps (#{steps.size}), falling back to static sort"
+
+        @steps.map { |s| [s.score, s] }.sort_by { |(score, _)| score }.each do |(score, step)|
+          yield step, score
+        end
+
+        return
+      end
+
+      steps = Set.new(@steps)
+
+      loop do
+        return if steps.empty?
+
+        # if the permission hasn't yet been enabled and we only have
+        # prevent steps left, we short-circuit the state here
+        @state.prevent! if !@state.enabled? && steps.all?(&:prevent?)
+
+        lowest_score = Float::INFINITY
+        next_step = nil
+
+        steps.each do |step|
+          score = step.score
+          if score < lowest_score
+            next_step = step
+            lowest_score = score
+          end
+        end
+
+        steps.delete(next_step)
+
+        yield next_step, lowest_score
+      end
+    end
+
+    # Formatter for debugging output.
+    def inspect_step(step, original_score, passed)
+      symbol =
+        case passed
+        when true then '+'
+        when false then '-'
+        when nil then ' '
+        end
+
+      "#{symbol} [#{original_score.to_i}] #{step.repr}\n"
+    end
+  end
+end
diff --git a/lib/declarative_policy/step.rb b/lib/declarative_policy/step.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3469fe9f991a352d4906982db03441c3a7549c3e
--- /dev/null
+++ b/lib/declarative_policy/step.rb
@@ -0,0 +1,86 @@
+module DeclarativePolicy
+  # This object represents one step in the runtime decision of whether
+  # an ability is allowed. It contains a Rule and a context (instance
+  # of DeclarativePolicy::Base), which contains the user, the subject,
+  # and the cache. It also contains an "action", which is the symbol
+  # :prevent or :enable.
+  class Step
+    attr_reader :context, :rule, :action
+    def initialize(context, rule, action)
+      @context = context
+      @rule = rule
+      @action = action
+    end
+
+    # In the flattening process, duplicate steps may be generated in the
+    # same rule. This allows us to eliminate those (see Runner#steps_by_score
+    # and note its use of a Set)
+    def ==(other)
+      @context == other.context && @rule == other.rule && @action == other.action
+    end
+
+    # In the runner, steps are sorted dynamically by score, so that
+    # we are sure to compute them in close to the optimal order.
+    #
+    # See also Rule#score, ManifestCondition#score, and Runner#steps_by_score.
+    def score
+      # we slightly prefer the preventative actions
+      # since they are more likely to short-circuit
+      case @action
+      when :prevent
+        @rule.score(@context) * (7.0 / 8)
+      when :enable
+        @rule.score(@context)
+      end
+    end
+
+    def with_action(action)
+      Step.new(@context, @rule, action)
+    end
+
+    def enable?
+      @action == :enable
+    end
+
+    def prevent?
+      @action == :prevent
+    end
+
+    # This rather complex method allows us to split rules into parts so that
+    # they can be sorted independently for better optimization
+    def flattened(roots)
+      case @rule
+      when Rule::Or
+        # A single `Or` step is the same as each of its elements as separate steps
+        @rule.rules.flat_map { |r| Step.new(@context, r, @action).flattened(roots) }
+      when Rule::Ability
+        # This looks like a weird micro-optimization but it buys us quite a lot
+        # in some cases. If we depend on an Ability (i.e. a `can?(...)` rule),
+        # and that ability *only* has :enable actions (modulo some actions that
+        # we already have taken care of), then its rules can be safely inlined.
+        steps = @context.runner(@rule.ability).steps.reject { |s| roots.include?(s) }
+
+        if steps.all?(&:enable?)
+          # in the case that we are a :prevent step, each inlined step becomes
+          # an independent :prevent, even though it was an :enable in its initial
+          # context.
+          steps.map! { |s| s.with_action(:prevent) } if prevent?
+
+          steps.flat_map { |s| s.flattened(roots) }
+        else
+          [self]
+        end
+      else
+        [self]
+      end
+    end
+
+    def pass?
+      @rule.pass?(@context)
+    end
+
+    def repr
+      "#{@action} when #{@rule.repr} (#{@context.repr})"
+    end
+  end
+end
diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb
index dd864eea3fa09c8ff72d8a1977fa8800686de710..721ed97bb6b6443e5f9513479074ad3cc9a0126b 100644
--- a/lib/extracts_path.rb
+++ b/lib/extracts_path.rb
@@ -126,8 +126,7 @@ module ExtractsPath
     raise InvalidPathError unless @commit
 
     @hex_path = Digest::SHA1.hexdigest(@path)
-    @logs_path = logs_file_namespace_project_ref_path(@project.namespace,
-                                                      @project, @ref, @path)
+    @logs_path = logs_file_project_ref_path(@project, @ref, @path)
 
   rescue RuntimeError, NoMethodError, InvalidPathError
     render_404
diff --git a/lib/feature.rb b/lib/feature.rb
index 5650a1c13343682b6a9f05506c8550b7c15b1cfb..363f66ba60eec2be663164881d412adb7abdf62c 100644
--- a/lib/feature.rb
+++ b/lib/feature.rb
@@ -12,6 +12,8 @@ class Feature
   end
 
   class << self
+    delegate :group, to: :flipper
+
     def all
       flipper.features.to_a
     end
@@ -27,19 +29,25 @@ class Feature
       all.map(&:name).include?(feature.name)
     end
 
-    def enabled?(key)
-      get(key).enabled?
+    def enabled?(key, thing = nil)
+      get(key).enabled?(thing)
+    end
+
+    def enable(key, thing = true)
+      get(key).enable(thing)
     end
 
-    def enable(key)
-      get(key).enable
+    def disable(key, thing = false)
+      get(key).disable(thing)
     end
 
-    def disable(key)
-      get(key).disable
+    def enable_group(key, group)
+      get(key).enable_group(group)
     end
 
-    private
+    def disable_group(key, group)
+      get(key).disable_group(group)
+    end
 
     def flipper
       @flipper ||= begin
diff --git a/lib/github/import.rb b/lib/github/import.rb
index b20614b30602d64912eafc307d8c7beb0ea3a971..ff5d7db2705b0ee207c35abdde510bf72a1d68f6 100644
--- a/lib/github/import.rb
+++ b/lib/github/import.rb
@@ -172,7 +172,7 @@ module Github
           next unless merge_request.new_record? && pull_request.valid?
 
           begin
-            restore_branches(pull_request)
+            pull_request.restore_branches!
 
             author_id   = user_id(pull_request.author, project.creator_id)
             description = format_description(pull_request.description, pull_request.author)
@@ -208,7 +208,7 @@ module Github
           rescue => e
             error(:pull_request, pull_request.url, e.message)
           ensure
-            clean_up_restored_branches(pull_request)
+            pull_request.remove_restored_branches!
           end
         end
 
@@ -325,32 +325,6 @@ module Github
       end
     end
 
-    def restore_branches(pull_request)
-      restore_source_branch(pull_request) unless pull_request.source_branch_exists?
-      restore_target_branch(pull_request) unless pull_request.target_branch_exists?
-    end
-
-    def restore_source_branch(pull_request)
-      repository.create_branch(pull_request.source_branch_name, pull_request.source_branch_sha)
-    end
-
-    def restore_target_branch(pull_request)
-      repository.create_branch(pull_request.target_branch_name, pull_request.target_branch_sha)
-    end
-
-    def remove_branch(name)
-      repository.delete_branch(name)
-    rescue Rugged::ReferenceError
-      errors << { type: :branch, url: nil, error: "Could not clean up restored branch: #{name}" }
-    end
-
-    def clean_up_restored_branches(pull_request)
-      return if pull_request.opened?
-
-      remove_branch(pull_request.source_branch_name) unless pull_request.source_branch_exists?
-      remove_branch(pull_request.target_branch_name) unless pull_request.target_branch_exists?
-    end
-
     def label_ids(labels)
       labels.map { |attrs| cached[:label_ids][attrs.fetch('name')] }.compact
     end
diff --git a/lib/github/representation/branch.rb b/lib/github/representation/branch.rb
index d1dac6944f09da490917981891ee58bc69e00cdf..c6fa928d565f988a20a7ba9db08faa507f21f590 100644
--- a/lib/github/representation/branch.rb
+++ b/lib/github/representation/branch.rb
@@ -26,13 +26,25 @@ module Github
       end
 
       def exists?
-        branch_exists? && commit_exists?
+        @exists ||= branch_exists? && commit_exists?
       end
 
       def valid?
         sha.present? && ref.present?
       end
 
+      def restore!(name)
+        repository.create_branch(name, sha)
+      rescue Gitlab::Git::Repository::InvalidRef => e
+        Rails.logger.error("#{self.class.name}: Could not restore branch #{name}: #{e}")
+      end
+
+      def remove!(name)
+        repository.delete_branch(name)
+      rescue Rugged::ReferenceError => e
+        Rails.logger.error("#{self.class.name}: Could not remove branch #{name}: #{e}")
+      end
+
       private
 
       def branch_exists?
diff --git a/lib/github/representation/pull_request.rb b/lib/github/representation/pull_request.rb
index ac9c8283b4bc9fd5a72ccbdf53022ec71ca442ca..55461097e8a9ee05b46c29b556f3b4ab57366d3b 100644
--- a/lib/github/representation/pull_request.rb
+++ b/lib/github/representation/pull_request.rb
@@ -1,8 +1,6 @@
 module Github
   module Representation
     class PullRequest < Representation::Issuable
-      attr_reader :project
-
       delegate :user, :repo, :ref, :sha, to: :source_branch, prefix: true
       delegate :user, :exists?, :repo, :ref, :sha, :short_sha, to: :target_branch, prefix: true
 
@@ -10,10 +8,6 @@ module Github
         project
       end
 
-      def source_branch_exists?
-        !cross_project? && source_branch.exists?
-      end
-
       def source_branch_name
         @source_branch_name ||=
           if cross_project? || !source_branch_exists?
@@ -23,6 +17,12 @@ module Github
           end
       end
 
+      def source_branch_exists?
+        return @source_branch_exists if defined?(@source_branch_exists)
+
+        @source_branch_exists = !cross_project? && source_branch.exists?
+      end
+
       def target_project
         project
       end
@@ -31,6 +31,10 @@ module Github
         @target_branch_name ||= target_branch_exists? ? target_branch_ref : target_branch_name_prefixed
       end
 
+      def target_branch_exists?
+        @target_branch_exists ||= target_branch.exists?
+      end
+
       def state
         return 'merged' if raw['state'] == 'closed' && raw['merged_at'].present?
         return 'closed' if raw['state'] == 'closed'
@@ -46,6 +50,18 @@ module Github
         source_branch.valid? && target_branch.valid?
       end
 
+      def restore_branches!
+        restore_source_branch!
+        restore_target_branch!
+      end
+
+      def remove_restored_branches!
+        return if opened?
+
+        remove_source_branch!
+        remove_target_branch!
+      end
+
       private
 
       def project
@@ -73,6 +89,32 @@ module Github
 
         source_branch_repo.id != target_branch_repo.id
       end
+
+      def restore_source_branch!
+        return if source_branch_exists?
+
+        source_branch.restore!(source_branch_name)
+      end
+
+      def restore_target_branch!
+        return if target_branch_exists?
+
+        target_branch.restore!(target_branch_name)
+      end
+
+      def remove_source_branch!
+        # We should remove the source/target branches only if they were
+        # restored. Otherwise, we'll remove branches like 'master' that
+        # target_branch_exists? returns true. In other words, we need
+        # to clean up only the restored branches that (source|target)_branch_exists?
+        # returns false for the first time it has been called, because of
+        # this that is important to memoize these values.
+        source_branch.remove!(source_branch_name) unless source_branch_exists?
+      end
+
+      def remove_target_branch!
+        target_branch.remove!(target_branch_name) unless target_branch_exists?
+      end
     end
   end
 end
diff --git a/lib/gitlab/allowable.rb b/lib/gitlab/allowable.rb
index e4f7cad2b7902954d663866a2efbf31db92bd794..45c2b01dd8fa75bf8c78ed9aa9a8bfa62391c5f8 100644
--- a/lib/gitlab/allowable.rb
+++ b/lib/gitlab/allowable.rb
@@ -1,7 +1,7 @@
 module Gitlab
   module Allowable
-    def can?(user, action, subject = :global)
-      Ability.allowed?(user, action, subject)
+    def can?(*args)
+      Ability.allowed?(*args)
     end
   end
 end
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 3933c3b04dd04a2db79157875fca3208e16046b9..ccb5d886bab8b16d3840d096fa55b8c3c2d16f83 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -130,13 +130,13 @@ module Gitlab
 
         token = PersonalAccessTokensFinder.new(state: 'active').find_by(token: password)
 
-        if token && valid_scoped_token?(token, AVAILABLE_SCOPES.map(&:to_s))
+        if token && valid_scoped_token?(token, AVAILABLE_SCOPES)
           Gitlab::Auth::Result.new(token.user, nil, :personal_token, abilities_for_scope(token.scopes))
         end
       end
 
       def valid_oauth_token?(token)
-        token && token.accessible? && valid_scoped_token?(token, ["api"])
+        token && token.accessible? && valid_scoped_token?(token, [:api])
       end
 
       def valid_scoped_token?(token, scopes)
diff --git a/lib/gitlab/background_migration.rb b/lib/gitlab/background_migration.rb
index 914a3b72abd0b56c06cc24693e8f7ab8f7276488..d95ecd7b291278fa887595eaf0f8a4dbab4f0189 100644
--- a/lib/gitlab/background_migration.rb
+++ b/lib/gitlab/background_migration.rb
@@ -5,8 +5,8 @@ module Gitlab
     #
     # steal_class - The name of the class for which to steal jobs.
     def self.steal(steal_class)
-      queue = Sidekiq::Queue.
-        new(BackgroundMigrationWorker.sidekiq_options['queue'])
+      queue = Sidekiq::Queue
+        .new(BackgroundMigrationWorker.sidekiq_options['queue'])
 
       queue.each do |job|
         migration_class, migration_args = job.args
diff --git a/lib/gitlab/badge/build/metadata.rb b/lib/gitlab/badge/build/metadata.rb
index f87a7b7942ef7cdba6fbb37f6513b0d76d127054..2ee35a0d4c16155b34216418c8752bf630d64758 100644
--- a/lib/gitlab/badge/build/metadata.rb
+++ b/lib/gitlab/badge/build/metadata.rb
@@ -15,12 +15,11 @@ module Gitlab
         end
 
         def image_url
-          build_namespace_project_badges_url(@project.namespace,
-                                             @project, @ref, format: :svg)
+          build_project_badges_url(@project, @ref, format: :svg)
         end
 
         def link_url
-          namespace_project_commits_url(@project.namespace, @project, id: @ref)
+          project_commits_url(@project, id: @ref)
         end
       end
     end
diff --git a/lib/gitlab/badge/coverage/metadata.rb b/lib/gitlab/badge/coverage/metadata.rb
index 5358818562298a4af49697d8e8708fb155b9566d..e898f5d790e73e0b2951eefc3f9e71155f193447 100644
--- a/lib/gitlab/badge/coverage/metadata.rb
+++ b/lib/gitlab/badge/coverage/metadata.rb
@@ -16,13 +16,11 @@ module Gitlab
         end
 
         def image_url
-          coverage_namespace_project_badges_url(@project.namespace,
-                                                @project, @ref,
-                                                format: :svg)
+          coverage_project_badges_url(@project, @ref, format: :svg)
         end
 
         def link_url
-          namespace_project_commits_url(@project.namespace, @project, id: @ref)
+          project_commits_url(@project, @ref)
         end
       end
     end
diff --git a/lib/gitlab/badge/metadata.rb b/lib/gitlab/badge/metadata.rb
index 4a049ef758d180b6528eea0fb0724444a39b4bca..86c193650fb7f0350af288efb1bf6763b977a31c 100644
--- a/lib/gitlab/badge/metadata.rb
+++ b/lib/gitlab/badge/metadata.rb
@@ -4,7 +4,7 @@ module Gitlab
     # Abstract class for badge metadata
     #
     class Metadata
-      include Gitlab::Application.routes.url_helpers
+      include Gitlab::Routing.url_helpers
       include ActionView::Helpers::AssetTagHelper
       include ActionView::Helpers::UrlHelper
 
diff --git a/lib/gitlab/cache/ci/project_pipeline_status.rb b/lib/gitlab/cache/ci/project_pipeline_status.rb
index 4fc9a075edc864e4296a4c54ed52de112b3233e8..9c2e09943b02b95d089829759097a03fa70bb9f5 100644
--- a/lib/gitlab/cache/ci/project_pipeline_status.rb
+++ b/lib/gitlab/cache/ci/project_pipeline_status.rb
@@ -50,8 +50,8 @@ module Gitlab
             ref: pipeline.ref
           }
 
-          new(pipeline.project, pipeline_info: pipeline_info).
-            store_in_cache_if_needed
+          new(pipeline.project, pipeline_info: pipeline_info)
+            .store_in_cache_if_needed
         end
 
         def initialize(project, pipeline_info: {}, loaded_from_cache: nil)
diff --git a/lib/gitlab/ci/config/entry/cache.rb b/lib/gitlab/ci/config/entry/cache.rb
index f074df9c7a1d789bfc813c094fc5020adec612aa..d7e09acbbf348c387fa013a9ad52da01bd3a1569 100644
--- a/lib/gitlab/ci/config/entry/cache.rb
+++ b/lib/gitlab/ci/config/entry/cache.rb
@@ -7,11 +7,14 @@ module Gitlab
         #
         class Cache < Node
           include Configurable
+          include Attributable
 
-          ALLOWED_KEYS = %i[key untracked paths].freeze
+          ALLOWED_KEYS = %i[key untracked paths policy].freeze
+          DEFAULT_POLICY = 'pull-push'.freeze
 
           validations do
             validates :config, allowed_keys: ALLOWED_KEYS
+            validates :policy, inclusion: { in: %w[pull-push push pull], message: 'should be pull-push, push, or pull' }, allow_blank: true
           end
 
           entry :key, Entry::Key,
@@ -25,8 +28,15 @@ module Gitlab
 
           helpers :key
 
+          attributes :policy
+
           def value
-            super.merge(key: key_value)
+            result = super
+
+            result[:key] = key_value
+            result[:policy] = policy || DEFAULT_POLICY
+
+            result
           end
         end
       end
diff --git a/lib/gitlab/ci/config/entry/image.rb b/lib/gitlab/ci/config/entry/image.rb
index 897dcff80122eb4cbdfe52a08569c18287ca5d5b..6555c5891736aa9eba06abeacf27e33682c425da 100644
--- a/lib/gitlab/ci/config/entry/image.rb
+++ b/lib/gitlab/ci/config/entry/image.rb
@@ -15,7 +15,7 @@ module Gitlab
             validates :config, allowed_keys: ALLOWED_KEYS
 
             validates :name, type: String, presence: true
-            validates :entrypoint, type: String, allow_nil: true
+            validates :entrypoint, array_of_strings: true, allow_nil: true
           end
 
           def hash?
diff --git a/lib/gitlab/ci/config/entry/service.rb b/lib/gitlab/ci/config/entry/service.rb
index b52faf48b58a63a139f895bab9f711ade8d707fe..3e2ebcff31a09df095433da33becc11221d7098b 100644
--- a/lib/gitlab/ci/config/entry/service.rb
+++ b/lib/gitlab/ci/config/entry/service.rb
@@ -15,8 +15,8 @@ module Gitlab
             validates :config, allowed_keys: ALLOWED_KEYS
 
             validates :name, type: String, presence: true
-            validates :entrypoint, type: String, allow_nil: true
-            validates :command, type: String, allow_nil: true
+            validates :entrypoint, array_of_strings: true, allow_nil: true
+            validates :command, array_of_strings: true, allow_nil: true
             validates :alias, type: String, allow_nil: true
           end
 
diff --git a/lib/gitlab/ci/pipeline_duration.rb b/lib/gitlab/ci/pipeline_duration.rb
index a210e76acaa41baa7658853232b208149b14b311..3208cc2bef64aaac1fd75bb832e073f6f880a590 100644
--- a/lib/gitlab/ci/pipeline_duration.rb
+++ b/lib/gitlab/ci/pipeline_duration.rb
@@ -87,8 +87,8 @@ module Gitlab
 
       def from_pipeline(pipeline)
         status = %w[success failed running canceled]
-        builds = pipeline.builds.latest.
-          where(status: status).where.not(started_at: nil).order(:started_at)
+        builds = pipeline.builds.latest
+          .where(status: status).where.not(started_at: nil).order(:started_at)
 
         from_builds(builds)
       end
diff --git a/lib/gitlab/ci/status/build/cancelable.rb b/lib/gitlab/ci/status/build/cancelable.rb
index 439ef0ce0153228aa10b41fdd6149290a080881e..8ad3e57e59db1ce09e8c0fe16a5eb39dbae8b87a 100644
--- a/lib/gitlab/ci/status/build/cancelable.rb
+++ b/lib/gitlab/ci/status/build/cancelable.rb
@@ -12,9 +12,7 @@ module Gitlab
           end
 
           def action_path
-            cancel_namespace_project_job_path(subject.project.namespace,
-                                                subject.project,
-                                                subject)
+            cancel_project_job_path(subject.project, subject)
           end
 
           def action_method
diff --git a/lib/gitlab/ci/status/build/common.rb b/lib/gitlab/ci/status/build/common.rb
index b173c23fba4040cf6bb3c8be639350af0ea82a44..c0c7c7f5b5dac6764d8eebd64a3dc8838645c8ad 100644
--- a/lib/gitlab/ci/status/build/common.rb
+++ b/lib/gitlab/ci/status/build/common.rb
@@ -8,9 +8,7 @@ module Gitlab
           end
 
           def details_path
-            namespace_project_job_path(subject.project.namespace,
-                                         subject.project,
-                                         subject)
+            project_job_path(subject.project, subject)
           end
         end
       end
diff --git a/lib/gitlab/ci/status/build/play.rb b/lib/gitlab/ci/status/build/play.rb
index e80f32637940689ad431f92196180e50af45837d..c77265435997a1584e35cf94e32a4415457176fd 100644
--- a/lib/gitlab/ci/status/build/play.rb
+++ b/lib/gitlab/ci/status/build/play.rb
@@ -20,9 +20,7 @@ module Gitlab
           end
 
           def action_path
-            play_namespace_project_job_path(subject.project.namespace,
-                                              subject.project,
-                                              subject)
+            play_project_job_path(subject.project, subject)
           end
 
           def action_method
diff --git a/lib/gitlab/ci/status/build/retryable.rb b/lib/gitlab/ci/status/build/retryable.rb
index 56303e4cb1762ce9fc5f21bf7eba61cee6255cda..8c8fdc56d752562bbd97f9c2e52d379347a83993 100644
--- a/lib/gitlab/ci/status/build/retryable.rb
+++ b/lib/gitlab/ci/status/build/retryable.rb
@@ -16,9 +16,7 @@ module Gitlab
           end
 
           def action_path
-            retry_namespace_project_job_path(subject.project.namespace,
-                                               subject.project,
-                                               subject)
+            retry_project_job_path(subject.project, subject)
           end
 
           def action_method
diff --git a/lib/gitlab/ci/status/build/stop.rb b/lib/gitlab/ci/status/build/stop.rb
index 2778d6f3b523316518f163682a48a79c85044f74..d464738deafba9b681320d3eec35ddfc771d4b19 100644
--- a/lib/gitlab/ci/status/build/stop.rb
+++ b/lib/gitlab/ci/status/build/stop.rb
@@ -20,9 +20,7 @@ module Gitlab
           end
 
           def action_path
-            play_namespace_project_job_path(subject.project.namespace,
-                                              subject.project,
-                                              subject)
+            play_project_job_path(subject.project, subject)
           end
 
           def action_method
diff --git a/lib/gitlab/ci/status/pipeline/common.rb b/lib/gitlab/ci/status/pipeline/common.rb
index 76bfd18bf4040f369e53a751d22d8f66d92eb337..61bb07beb0fd6cff23f87a2619712e68976b2cd2 100644
--- a/lib/gitlab/ci/status/pipeline/common.rb
+++ b/lib/gitlab/ci/status/pipeline/common.rb
@@ -8,9 +8,7 @@ module Gitlab
           end
 
           def details_path
-            namespace_project_pipeline_path(subject.project.namespace,
-                                            subject.project,
-                                            subject)
+            project_pipeline_path(subject.project, subject)
           end
 
           def has_action?
diff --git a/lib/gitlab/ci/status/stage/common.rb b/lib/gitlab/ci/status/stage/common.rb
index 7852f492e1d0738b8f2e33af2e7412dd0c2e8356..bc99d92534795a4d352362a99aad8fcf0562932c 100644
--- a/lib/gitlab/ci/status/stage/common.rb
+++ b/lib/gitlab/ci/status/stage/common.rb
@@ -8,10 +8,7 @@ module Gitlab
           end
 
           def details_path
-            namespace_project_pipeline_path(subject.project.namespace,
-                                            subject.project,
-                                            subject.pipeline,
-                                            anchor: subject.name)
+            project_pipeline_path(subject.project, subject.pipeline, anchor: subject.name)
           end
 
           def has_action?
diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb
index 75a213ef752d100258d4486bfed5376643e5212f..d2b4e6e209e568f19f556814c860e4f2ff105729 100644
--- a/lib/gitlab/conflict/file.rb
+++ b/lib/gitlab/conflict/file.rb
@@ -205,9 +205,7 @@ module Gitlab
           old_path: their_path,
           new_path: our_path,
           blob_icon: file_type_icon_class('file', our_mode, our_path),
-          blob_path: namespace_project_blob_path(merge_request.project.namespace,
-                                                 merge_request.project,
-                                                 ::File.join(merge_request.diff_refs.head_sha, our_path))
+          blob_path: project_blob_path(merge_request.project, ::File.join(merge_request.diff_refs.head_sha, our_path))
         }
 
         json_hash.tap do |json_hash|
@@ -223,11 +221,10 @@ module Gitlab
       end
 
       def content_path
-        conflict_for_path_namespace_project_merge_request_path(merge_request.project.namespace,
-                                                               merge_request.project,
-                                                               merge_request,
-                                                               old_path: their_path,
-                                                               new_path: our_path)
+        conflict_for_path_project_merge_request_path(merge_request.project,
+                                                     merge_request,
+                                                     old_path: their_path,
+                                                     new_path: our_path)
       end
 
       # Don't try to print merge_request or repository.
diff --git a/lib/gitlab/conflict/file_collection.rb b/lib/gitlab/conflict/file_collection.rb
index 6e73361cad13f9cf9383571077c79292b98ba373..1611eba31da9d1da8dcdb51311981e8b67665165 100644
--- a/lib/gitlab/conflict/file_collection.rb
+++ b/lib/gitlab/conflict/file_collection.rb
@@ -16,9 +16,9 @@ module Gitlab
           project = merge_request.source_project
 
           new(merge_request, project).tap do |file_collection|
-            project.
-              repository.
-              with_repo_branch_commit(merge_request.target_project.repository, merge_request.target_branch) do
+            project
+              .repository
+              .with_repo_branch_commit(merge_request.target_project.repository, merge_request.target_branch) do
 
               yield file_collection
             end
diff --git a/lib/gitlab/contributions_calendar.rb b/lib/gitlab/contributions_calendar.rb
index 060e013183fe9e2b24962ec00bd8dbbe150b03a8..bf557103cfd0892ae5c15b91627991b49c993c61 100644
--- a/lib/gitlab/contributions_calendar.rb
+++ b/lib/gitlab/contributions_calendar.rb
@@ -16,14 +16,14 @@ module Gitlab
       # Can't use Event.contributions here because we need to check 3 different
       # project_features for the (currently) 3 different contribution types
       date_from = 1.year.ago
-      repo_events = event_counts(date_from, :repository).
-        having(action: Event::PUSHED)
-      issue_events = event_counts(date_from, :issues).
-        having(action: [Event::CREATED, Event::CLOSED], target_type: "Issue")
-      mr_events = event_counts(date_from, :merge_requests).
-        having(action: [Event::MERGED, Event::CREATED, Event::CLOSED], target_type: "MergeRequest")
-      note_events = event_counts(date_from, :merge_requests).
-        having(action: [Event::COMMENTED], target_type: "Note")
+      repo_events = event_counts(date_from, :repository)
+        .having(action: Event::PUSHED)
+      issue_events = event_counts(date_from, :issues)
+        .having(action: [Event::CREATED, Event::CLOSED], target_type: "Issue")
+      mr_events = event_counts(date_from, :merge_requests)
+        .having(action: [Event::MERGED, Event::CREATED, Event::CLOSED], target_type: "MergeRequest")
+      note_events = event_counts(date_from, :merge_requests)
+        .having(action: [Event::COMMENTED], target_type: "Note")
 
       union = Gitlab::SQL::Union.new([repo_events, issue_events, mr_events, note_events])
       events = Event.find_by_sql(union.to_sql).map(&:attributes)
@@ -34,9 +34,9 @@ module Gitlab
     end
 
     def events_by_date(date)
-      events = Event.contributions.where(author_id: contributor.id).
-        where(created_at: date.beginning_of_day..date.end_of_day).
-        where(project_id: projects)
+      events = Event.contributions.where(author_id: contributor.id)
+        .where(created_at: date.beginning_of_day..date.end_of_day)
+        .where(project_id: projects)
 
       # Use visible_to_user? instead of the complicated logic in activity_dates
       # because we're only viewing the events for a single day.
@@ -60,20 +60,20 @@ module Gitlab
       # use IN(project_ids...) instead. It's the intersection of two users so
       # the list will be (relatively) short
       @contributed_project_ids ||= projects.uniq.pluck(:id)
-      authed_projects = Project.where(id: @contributed_project_ids).
-        with_feature_available_for_user(feature, current_user).
-        reorder(nil).
-        select(:id)
+      authed_projects = Project.where(id: @contributed_project_ids)
+        .with_feature_available_for_user(feature, current_user)
+        .reorder(nil)
+        .select(:id)
 
-      conditions = t[:created_at].gteq(date_from.beginning_of_day).
-        and(t[:created_at].lteq(Date.today.end_of_day)).
-        and(t[:author_id].eq(contributor.id))
+      conditions = t[:created_at].gteq(date_from.beginning_of_day)
+        .and(t[:created_at].lteq(Date.today.end_of_day))
+        .and(t[:author_id].eq(contributor.id))
 
-      Event.reorder(nil).
-        select(t[:project_id], t[:target_type], t[:action], 'date(created_at) AS date', 'count(id) as total_amount').
-        group(t[:project_id], t[:target_type], t[:action], 'date(created_at)').
-        where(conditions).
-        having(t[:project_id].in(Arel::Nodes::SqlLiteral.new(authed_projects.to_sql)))
+      Event.reorder(nil)
+        .select(t[:project_id], t[:target_type], t[:action], 'date(created_at) AS date', 'count(id) as total_amount')
+        .group(t[:project_id], t[:target_type], t[:action], 'date(created_at)')
+        .where(conditions)
+        .having(t[:project_id].in(Arel::Nodes::SqlLiteral.new(authed_projects.to_sql)))
     end
   end
 end
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index 48735fd197d68ef65c14ab9389a186ae4ec03e14..818b3d9c46b77f6eccde9fdeaba7f7668b9359cc 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -10,43 +10,49 @@ module Gitlab
 
     delegate :sidekiq_throttling_enabled?, to: :current_application_settings
 
-    def fake_application_settings
-      OpenStruct.new(::ApplicationSetting.defaults)
+    def fake_application_settings(defaults = ::ApplicationSetting.defaults)
+      FakeApplicationSettings.new(defaults)
     end
 
     private
 
     def ensure_application_settings!
-      unless ENV['IN_MEMORY_APPLICATION_SETTINGS'] == 'true'
-        settings = retrieve_settings_from_database?
-      end
+      return in_memory_application_settings if ENV['IN_MEMORY_APPLICATION_SETTINGS'] == 'true'
 
-      settings || in_memory_application_settings
+      cached_application_settings || uncached_application_settings
     end
 
-    def retrieve_settings_from_database?
-      settings = retrieve_settings_from_database_cache?
-      return settings if settings.present?
-
-      return fake_application_settings unless connect_to_db?
-
+    def cached_application_settings
       begin
-        db_settings = ::ApplicationSetting.current
-        # In case Redis isn't running or the Redis UNIX socket file is not available
+        ::ApplicationSetting.cached
       rescue ::Redis::BaseError, ::Errno::ENOENT
-        db_settings = ::ApplicationSetting.last
+        # In case Redis isn't running or the Redis UNIX socket file is not available
       end
-      db_settings || ::ApplicationSetting.create_from_defaults
     end
 
-    def retrieve_settings_from_database_cache?
+    def uncached_application_settings
+      return fake_application_settings unless connect_to_db?
+
+      # This loads from the database into the cache, so handle Redis errors
       begin
-        settings = ApplicationSetting.cached
+        db_settings = ::ApplicationSetting.current
       rescue ::Redis::BaseError, ::Errno::ENOENT
         # In case Redis isn't running or the Redis UNIX socket file is not available
-        settings = nil
       end
-      settings
+
+      # If there are pending migrations, it's possible there are columns that
+      # need to be added to the application settings. To prevent Rake tasks
+      # and other callers from failing, use any loaded settings and return
+      # defaults for missing columns.
+      if ActiveRecord::Migrator.needs_migration?
+        defaults = ::ApplicationSetting.defaults
+        defaults.merge!(db_settings.attributes.symbolize_keys) if db_settings.present?
+        return fake_application_settings(defaults)
+      end
+
+      return db_settings if db_settings.present?
+
+      ::ApplicationSetting.create_from_defaults || in_memory_application_settings
     end
 
     def in_memory_application_settings
@@ -62,8 +68,7 @@ module Gitlab
       active_db_connection = ActiveRecord::Base.connection.active? rescue false
 
       active_db_connection &&
-        ActiveRecord::Base.connection.table_exists?('application_settings') &&
-        !ActiveRecord::Migrator.needs_migration?
+        ActiveRecord::Base.connection.table_exists?('application_settings')
     rescue ActiveRecord::NoDatabaseError
       false
     end
diff --git a/lib/gitlab/cycle_analytics/base_query.rb b/lib/gitlab/cycle_analytics/base_query.rb
index d560dca45c8abbec50ecf5790b9fa0d9715e01e8..58729d3ced841ec5d956b5ef98609a8540b71b1f 100644
--- a/lib/gitlab/cycle_analytics/base_query.rb
+++ b/lib/gitlab/cycle_analytics/base_query.rb
@@ -12,17 +12,17 @@ module Gitlab
       end
 
       def stage_query
-        query = mr_closing_issues_table.join(issue_table).on(issue_table[:id].eq(mr_closing_issues_table[:issue_id])).
-          join(issue_metrics_table).on(issue_table[:id].eq(issue_metrics_table[:issue_id])).
-          where(issue_table[:project_id].eq(@project.id)).
-          where(issue_table[:deleted_at].eq(nil)).
-          where(issue_table[:created_at].gteq(@options[:from]))
+        query = mr_closing_issues_table.join(issue_table).on(issue_table[:id].eq(mr_closing_issues_table[:issue_id]))
+          .join(issue_metrics_table).on(issue_table[:id].eq(issue_metrics_table[:issue_id]))
+          .where(issue_table[:project_id].eq(@project.id))
+          .where(issue_table[:deleted_at].eq(nil))
+          .where(issue_table[:created_at].gteq(@options[:from]))
 
         # Load merge_requests
-        query = query.join(mr_table, Arel::Nodes::OuterJoin).
-          on(mr_table[:id].eq(mr_closing_issues_table[:merge_request_id])).
-          join(mr_metrics_table).
-          on(mr_table[:id].eq(mr_metrics_table[:merge_request_id]))
+        query = query.join(mr_table, Arel::Nodes::OuterJoin)
+          .on(mr_table[:id].eq(mr_closing_issues_table[:merge_request_id]))
+          .join(mr_metrics_table)
+          .on(mr_table[:id].eq(mr_metrics_table[:merge_request_id]))
 
         query
       end
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index d0bd129967143df816ae62e98e5f1033690688f8..d7dab584a447fdd6753101e483dc93c00dd222cb 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -83,6 +83,22 @@ module Gitlab
       end
     end
 
+    def self.bulk_insert(table, rows)
+      return if rows.empty?
+
+      keys = rows.first.keys
+      columns = keys.map { |key| connection.quote_column_name(key) }
+
+      tuples = rows.map do |row|
+        row.values_at(*keys).map { |value| connection.quote(value) }
+      end
+
+      connection.execute <<-EOF
+        INSERT INTO #{table} (#{columns.join(', ')})
+        VALUES #{tuples.map { |tuple| "(#{tuple.join(', ')})" }.join(', ')}
+      EOF
+    end
+
     # pool_size - The size of the DB pool.
     # host - An optional host name to use instead of the default one.
     def self.create_connection_pool(pool_size, host = nil)
diff --git a/lib/gitlab/database/median.rb b/lib/gitlab/database/median.rb
index 23890e5f4935dc94b55562d76278a86ad3bf7c89..059054ac9ffda2ccca8b9c2a5daa32844f19f43c 100644
--- a/lib/gitlab/database/median.rb
+++ b/lib/gitlab/database/median.rb
@@ -29,10 +29,10 @@ module Gitlab
       end
 
       def mysql_median_datetime_sql(arel_table, query_so_far, column_sym)
-        query = arel_table.
-                from(arel_table.project(Arel.sql('*')).order(arel_table[column_sym]).as(arel_table.table_name)).
-                project(average([arel_table[column_sym]], 'median')).
-                where(
+        query = arel_table
+                .from(arel_table.project(Arel.sql('*')).order(arel_table[column_sym]).as(arel_table.table_name))
+                .project(average([arel_table[column_sym]], 'median'))
+                .where(
                   Arel::Nodes::Between.new(
                     Arel.sql("(select @row_id := @row_id + 1)"),
                     Arel::Nodes::And.new(
@@ -67,8 +67,8 @@ module Gitlab
         cte_table = Arel::Table.new("ordered_records")
         cte = Arel::Nodes::As.new(
           cte_table,
-          arel_table.
-            project(
+          arel_table
+            .project(
               arel_table[column_sym].as(column_sym.to_s),
               Arel::Nodes::Over.new(Arel::Nodes::NamedFunction.new("row_number", []),
                                     Arel::Nodes::Window.new.order(arel_table[column_sym])).as('row_id'),
@@ -79,8 +79,8 @@ module Gitlab
         # From the CTE, select either the middle row or the middle two rows (this is accomplished
         # by 'where cte.row_id between cte.ct / 2.0 AND cte.ct / 2.0 + 1'). Find the average of the
         # selected rows, and this is the median value.
-        cte_table.project(average([extract_epoch(cte_table[column_sym])], "median")).
-          where(
+        cte_table.project(average([extract_epoch(cte_table[column_sym])], "median"))
+          .where(
             Arel::Nodes::Between.new(
               cte_table[:row_id],
               Arel::Nodes::And.new(
@@ -88,9 +88,9 @@ module Gitlab
                  (cte_table[:ct] / Arel.sql('2.0') + 1)]
               )
             )
-          ).
-          with(query_so_far, cte).
-          to_sql
+          )
+          .with(query_so_far, cte)
+          .to_sql
       end
 
       private
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index cd85f961242c086c98e898206864c6c05b884b54..0643c56db9bc7d53f4cf0762a1b7a10c84ae153b 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -222,6 +222,12 @@ module Gitlab
       #
       # rubocop: disable Metrics/AbcSize
       def update_column_in_batches(table, column, value)
+        if transaction_open?
+          raise 'update_column_in_batches can not be run inside a transaction, ' \
+            'you can disable transactions by calling disable_ddl_transaction! ' \
+            'in the body of your migration class'
+        end
+
         table = Arel::Table.new(table)
 
         count_arel = table.project(Arel.star.count.as('count'))
@@ -233,25 +239,31 @@ module Gitlab
 
         # Update in batches of 5% until we run out of any rows to update.
         batch_size = ((total / 100.0) * 5.0).ceil
+        max_size = 1000
+
+        # The upper limit is 1000 to ensure we don't lock too many rows. For
+        # example, for "merge_requests" even 1% of the table is around 35 000
+        # rows for GitLab.com.
+        batch_size = max_size if batch_size > max_size
 
         start_arel = table.project(table[:id]).order(table[:id].asc).take(1)
         start_arel = yield table, start_arel if block_given?
         start_id = exec_query(start_arel.to_sql).to_hash.first['id'].to_i
 
         loop do
-          stop_arel = table.project(table[:id]).
-            where(table[:id].gteq(start_id)).
-            order(table[:id].asc).
-            take(1).
-            skip(batch_size)
+          stop_arel = table.project(table[:id])
+            .where(table[:id].gteq(start_id))
+            .order(table[:id].asc)
+            .take(1)
+            .skip(batch_size)
 
           stop_arel = yield table, stop_arel if block_given?
           stop_row = exec_query(stop_arel.to_sql).to_hash.first
 
-          update_arel = Arel::UpdateManager.new(ActiveRecord::Base).
-            table(table).
-            set([[table[column], value]]).
-            where(table[:id].gteq(start_id))
+          update_arel = Arel::UpdateManager.new(ActiveRecord::Base)
+            .table(table)
+            .set([[table[column], value]])
+            .where(table[:id].gteq(start_id))
 
           if stop_row
             stop_id = stop_row['id'].to_i
@@ -580,15 +592,15 @@ module Gitlab
         quoted_replacement = Arel::Nodes::Quoted.new(replacement.to_s)
 
         if Database.mysql?
-          locate = Arel::Nodes::NamedFunction.
-            new('locate', [quoted_pattern, column])
-          insert_in_place = Arel::Nodes::NamedFunction.
-            new('insert', [column, locate, pattern.size, quoted_replacement])
+          locate = Arel::Nodes::NamedFunction
+            .new('locate', [quoted_pattern, column])
+          insert_in_place = Arel::Nodes::NamedFunction
+            .new('insert', [column, locate, pattern.size, quoted_replacement])
 
           Arel::Nodes::SqlLiteral.new(insert_in_place.to_sql)
         else
-          replace = Arel::Nodes::NamedFunction.
-            new("regexp_replace", [column, quoted_pattern, quoted_replacement])
+          replace = Arel::Nodes::NamedFunction
+            .new("regexp_replace", [column, quoted_pattern, quoted_replacement])
           Arel::Nodes::SqlLiteral.new(replace.to_sql)
         end
       end
diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1.rb
index 89530082cd2d6aecdada8bd31414dc7fa38ee29a..f333ff22300aba0a5d269090b029cfa8d43d9112 100644
--- a/lib/gitlab/database/rename_reserved_paths_migration/v1.rb
+++ b/lib/gitlab/database/rename_reserved_paths_migration/v1.rb
@@ -29,6 +29,11 @@ module Gitlab
           paths = Array(paths)
           RenameNamespaces.new(paths, self).rename_namespaces(type: :top_level)
         end
+
+        def revert_renames
+          RenameProjects.new([], self).revert_renames
+          RenameNamespaces.new([], self).revert_renames
+        end
       end
     end
   end
diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb
index d60fd4bb5511f62e4a9c566b5927c487c22be556..33f8939bc61fc2abefdfd1cbd698d8206ca79599 100644
--- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb
+++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb
@@ -6,7 +6,10 @@ module Gitlab
           attr_reader :paths, :migration
 
           delegate :update_column_in_batches,
+                   :execute,
                    :replace_sql,
+                   :quote_string,
+                   :say,
                    to: :migration
 
           def initialize(paths, migration)
@@ -26,24 +29,45 @@ module Gitlab
             new_path = rename_path(namespace_path, old_path)
             new_full_path = join_routable_path(namespace_path, new_path)
 
+            perform_rename(routable, old_full_path, new_full_path)
+
+            [old_full_path, new_full_path]
+          end
+
+          def perform_rename(routable, old_full_path, new_full_path)
             # skips callbacks & validations
-            routable.class.where(id: routable).
-              update_all(path: new_path)
+            new_path = new_full_path.split('/').last
+            routable.class.where(id: routable)
+              .update_all(path: new_path)
 
             rename_routes(old_full_path, new_full_path)
-
-            [old_full_path, new_full_path]
           end
 
           def rename_routes(old_full_path, new_full_path)
+            routes = Route.arel_table
+
+            quoted_old_full_path = quote_string(old_full_path)
+            quoted_old_wildcard_path = quote_string("#{old_full_path}/%")
+
+            filter = if Database.mysql?
+                       "lower(routes.path) = lower('#{quoted_old_full_path}') "\
+                       "OR routes.path LIKE '#{quoted_old_wildcard_path}'"
+                     else
+                       "routes.id IN "\
+                       "( SELECT routes.id FROM routes WHERE lower(routes.path) = lower('#{quoted_old_full_path}') "\
+                       "UNION SELECT routes.id FROM routes WHERE routes.path ILIKE '#{quoted_old_wildcard_path}' )"
+                     end
+
             replace_statement = replace_sql(Route.arel_table[:path],
                                             old_full_path,
                                             new_full_path)
 
-            update_column_in_batches(:routes, :path, replace_statement)  do |table, query|
-              path_or_children = table[:path].matches_any([old_full_path, "#{old_full_path}/%"])
-              query.where(path_or_children)
-            end
+            update = Arel::UpdateManager.new(ActiveRecord::Base)
+                       .table(routes)
+                       .set([[routes[:path], replace_statement]])
+                       .where(Arel::Nodes::SqlLiteral.new(filter))
+
+            execute(update.to_sql)
           end
 
           def rename_path(namespace_path, path_was)
@@ -86,32 +110,74 @@ module Gitlab
 
           def move_folders(directory, old_relative_path, new_relative_path)
             old_path = File.join(directory, old_relative_path)
-            return unless File.directory?(old_path)
+            unless File.directory?(old_path)
+              say "#{old_path} doesn't exist, skipping"
+              return
+            end
 
             new_path = File.join(directory, new_relative_path)
             FileUtils.mv(old_path, new_path)
           end
 
           def remove_cached_html_for_projects(project_ids)
-            update_column_in_batches(:projects, :description_html, nil) do |table, query|
-              query.where(table[:id].in(project_ids))
-            end
-
-            update_column_in_batches(:issues, :description_html, nil) do |table, query|
-              query.where(table[:project_id].in(project_ids))
+            project_ids.each do |project_id|
+              update_column_in_batches(:projects, :description_html, nil) do |table, query|
+                query.where(table[:id].eq(project_id))
+              end
+
+              update_column_in_batches(:issues, :description_html, nil) do |table, query|
+                query.where(table[:project_id].eq(project_id))
+              end
+
+              update_column_in_batches(:merge_requests, :description_html, nil) do |table, query|
+                query.where(table[:target_project_id].eq(project_id))
+              end
+
+              update_column_in_batches(:notes, :note_html, nil) do |table, query|
+                query.where(table[:project_id].eq(project_id))
+              end
+
+              update_column_in_batches(:milestones, :description_html, nil) do |table, query|
+                query.where(table[:project_id].eq(project_id))
+              end
             end
+          end
 
-            update_column_in_batches(:merge_requests, :description_html, nil) do |table, query|
-              query.where(table[:target_project_id].in(project_ids))
+          def track_rename(type, old_path, new_path)
+            key = redis_key_for_type(type)
+            Gitlab::Redis.with do |redis|
+              redis.lpush(key, [old_path, new_path].to_json)
+              redis.expire(key, 2.weeks.to_i)
             end
+            say "tracked rename: #{key}: #{old_path} -> #{new_path}"
+          end
 
-            update_column_in_batches(:notes, :note_html, nil) do |table, query|
-              query.where(table[:project_id].in(project_ids))
+          def reverts_for_type(type)
+            key = redis_key_for_type(type)
+
+            Gitlab::Redis.with do |redis|
+              failed_reverts = []
+
+              while rename_info = redis.lpop(key)
+                path_before_rename, path_after_rename = JSON.parse(rename_info)
+                say "renaming #{type} from #{path_after_rename} back to #{path_before_rename}"
+                begin
+                  yield(path_before_rename, path_after_rename)
+                rescue StandardError => e
+                  failed_reverts << rename_info
+                  say "Renaming #{type} from #{path_after_rename} back to "\
+                      "#{path_before_rename} failed. Review the error and try "\
+                      "again by running the `down` action. \n"\
+                      "#{e.message}: \n #{e.backtrace.join("\n")}"
+                end
+              end
+
+              failed_reverts.each { |rename_info| redis.lpush(key, rename_info) }
             end
+          end
 
-            update_column_in_batches(:milestones, :description_html, nil) do |table, query|
-              query.where(table[:project_id].in(project_ids))
-            end
+          def redis_key_for_type(type)
+            "rename:#{migration.name}:#{type}"
           end
 
           def file_storage?
diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb
index 2958ad4b8e5f63927653a4c94a6e5ecdb22dc573..05b86f32ce2260bf9c0de87f071f6f64a04316dd 100644
--- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb
+++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb
@@ -18,14 +18,20 @@ module Gitlab
                          when :top_level
                            MigrationClasses::Namespace.where(parent_id: nil)
                          end
-            with_paths = MigrationClasses::Route.arel_table[:path].
-                           matches_any(path_patterns)
+            with_paths = MigrationClasses::Route.arel_table[:path]
+                           .matches_any(path_patterns)
             namespaces.joins(:route).where(with_paths)
           end
 
           def rename_namespace(namespace)
             old_full_path, new_full_path = rename_path_for_routable(namespace)
 
+            track_rename('namespace', old_full_path, new_full_path)
+
+            rename_namespace_dependencies(namespace, old_full_path, new_full_path)
+          end
+
+          def rename_namespace_dependencies(namespace, old_full_path, new_full_path)
             move_repositories(namespace, old_full_path, new_full_path)
             move_uploads(old_full_path, new_full_path)
             move_pages(old_full_path, new_full_path)
@@ -33,6 +39,23 @@ module Gitlab
             remove_cached_html_for_projects(projects_for_namespace(namespace).map(&:id))
           end
 
+          def revert_renames
+            reverts_for_type('namespace') do |path_before_rename, current_path|
+              matches_path = MigrationClasses::Route.arel_table[:path].matches(current_path)
+              namespace = MigrationClasses::Namespace.joins(:route)
+                            .where(matches_path).first&.becomes(MigrationClasses::Namespace)
+
+              if namespace
+                perform_rename(namespace, current_path, path_before_rename)
+
+                rename_namespace_dependencies(namespace, current_path, path_before_rename)
+              else
+                say "Couldn't rename namespace from #{current_path} back to #{path_before_rename}, "\
+                    "namespace was renamed, or no longer exists at the expected path"
+              end
+            end
+          end
+
           def rename_user(old_username, new_username)
             MigrationClasses::User.where(username: old_username)
               .update_all(username: new_username)
@@ -52,15 +75,15 @@ module Gitlab
           end
 
           def repo_paths_for_namespace(namespace)
-            projects_for_namespace(namespace).distinct.select(:repository_storage).
-              map(&:repository_storage_path)
+            projects_for_namespace(namespace).distinct.select(:repository_storage)
+              .map(&:repository_storage_path)
           end
 
           def projects_for_namespace(namespace)
             namespace_ids = child_ids_for_parent(namespace, ids: [namespace.id])
-            namespace_or_children = MigrationClasses::Project.
-                                      arel_table[:namespace_id].
-                                      in(namespace_ids)
+            namespace_or_children = MigrationClasses::Project
+                                      .arel_table[:namespace_id]
+                                      .in(namespace_ids)
             MigrationClasses::Project.where(namespace_or_children)
           end
 
diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb
index 448717eb744d6c5e143721ce286967da80faa845..75a75f619538e3a182f9bafcb863ee798b207152 100644
--- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb
+++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb
@@ -16,12 +16,37 @@ module Gitlab
           def rename_project(project)
             old_full_path, new_full_path = rename_path_for_routable(project)
 
+            track_rename('project', old_full_path, new_full_path)
+
+            move_project_folders(project, old_full_path, new_full_path)
+          end
+
+          def move_project_folders(project, old_full_path, new_full_path)
             move_repository(project, old_full_path, new_full_path)
             move_repository(project, "#{old_full_path}.wiki", "#{new_full_path}.wiki")
             move_uploads(old_full_path, new_full_path)
             move_pages(old_full_path, new_full_path)
           end
 
+          def revert_renames
+            reverts_for_type('project') do |path_before_rename, current_path|
+              matches_path = MigrationClasses::Route.arel_table[:path].matches(current_path)
+              project = MigrationClasses::Project.joins(:route)
+                          .where(matches_path).first
+
+              if project
+                perform_rename(project, current_path, path_before_rename)
+
+                move_project_folders(project, current_path, path_before_rename)
+              else
+                say "Couldn't rename project from #{current_path} back to "\
+                    "#{path_before_rename}, project was renamed or no longer "\
+                    "exists at the expected path."
+
+              end
+            end
+          end
+
           def move_repository(project, old_path, new_path)
             unless gitlab_shell.mv_repository(project.repository_storage_path,
                                               old_path,
diff --git a/lib/gitlab/database/sha_attribute.rb b/lib/gitlab/database/sha_attribute.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d9400e04b83997e0e3ecd9fa289848ba47375942
--- /dev/null
+++ b/lib/gitlab/database/sha_attribute.rb
@@ -0,0 +1,34 @@
+module Gitlab
+  module Database
+    BINARY_TYPE = if Gitlab::Database.postgresql?
+                    # PostgreSQL defines its own class with slightly different
+                    # behaviour from the default Binary type.
+                    ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Bytea
+                  else
+                    ActiveRecord::Type::Binary
+                  end
+
+    # Class for casting binary data to hexadecimal SHA1 hashes (and vice-versa).
+    #
+    # Using ShaAttribute allows you to store SHA1 values as binary while still
+    # using them as if they were stored as string values. This gives you the
+    # ease of use of string values, but without the storage overhead.
+    class ShaAttribute < BINARY_TYPE
+      PACK_FORMAT = 'H*'.freeze
+
+      # Casts binary data to a SHA1 in hexadecimal.
+      def type_cast_from_database(value)
+        value = super
+
+        value ? value.unpack(PACK_FORMAT)[0] : nil
+      end
+
+      # Casts a SHA1 in hexadecimal to the proper binary format.
+      def type_cast_for_database(value)
+        arg = value ? [value].pack(PACK_FORMAT) : nil
+
+        super(arg)
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/dependency_linker/base_linker.rb b/lib/gitlab/dependency_linker/base_linker.rb
index 7bbd154eb03f8f7a49e4074c40b9372d8b20e875..d236058374144c465ca63efd8067d9838f1d8242 100644
--- a/lib/gitlab/dependency_linker/base_linker.rb
+++ b/lib/gitlab/dependency_linker/base_linker.rb
@@ -52,7 +52,7 @@ module Gitlab
       #   # Will link `user/repo` in `github: "user/repo"` or `:github => "user/repo"`
       def link_regex(regex, &url_proc)
         highlighted_lines.map!.with_index do |rich_line, i|
-          marker = StringRegexMarker.new(plain_lines[i], rich_line.html_safe)
+          marker = StringRegexMarker.new(plain_lines[i].chomp, rich_line.html_safe)
 
           marker.mark(regex, group: :name) do |text, left:, right:|
             url = yield(text)
diff --git a/lib/gitlab/dependency_linker/requirements_txt_linker.rb b/lib/gitlab/dependency_linker/requirements_txt_linker.rb
index 2e197e5cd94dd3c90f16080ac9869c532c073677..9c9620bc36a15721553e651b702a57821a457c60 100644
--- a/lib/gitlab/dependency_linker/requirements_txt_linker.rb
+++ b/lib/gitlab/dependency_linker/requirements_txt_linker.rb
@@ -6,7 +6,7 @@ module Gitlab
       private
 
       def link_dependencies
-        link_regex(/^(?<name>(?![a-z+]+:)[^#.-][^ ><=;\[]+)/) do |name|
+        link_regex(/^(?<name>(?![a-z+]+:)[^#.-][^ ><=~!;\[]+)/) do |name|
           "https://pypi.python.org/pypi/#{name}"
         end
 
diff --git a/lib/gitlab/diff/line.rb b/lib/gitlab/diff/line.rb
index bd52ae47e9ff0de69a472d59f61b9297cf0661fa..2d89ccfc354ac9f217813babe91a492cc15061c4 100644
--- a/lib/gitlab/diff/line.rb
+++ b/lib/gitlab/diff/line.rb
@@ -42,25 +42,25 @@ module Gitlab
       end
 
       def added?
-        type == 'new' || type == 'new-nonewline'
+        %w[new new-nonewline].include?(type)
       end
 
       def removed?
-        type == 'old' || type == 'old-nonewline'
-      end
-
-      def rich_text
-        @parent_file.highlight_lines! if @parent_file && !@rich_text
-
-        @rich_text
+        %w[old old-nonewline].include?(type)
       end
 
       def meta?
-        type == 'match'
+        %w[match new-nonewline old-nonewline].include?(type)
       end
 
       def discussable?
-        !['match', 'new-nonewline', 'old-nonewline'].include?(type)
+        !meta?
+      end
+
+      def rich_text
+        @parent_file.highlight_lines! if @parent_file && !@rich_text
+
+        @rich_text
       end
 
       def as_json(opts = nil)
diff --git a/lib/gitlab/diff/parallel_diff.rb b/lib/gitlab/diff/parallel_diff.rb
index 481536a380bc9118d5103141dbb198b54131a636..0cb26fa45c8acd8b7b39a1be268fb600a7449b2e 100644
--- a/lib/gitlab/diff/parallel_diff.rb
+++ b/lib/gitlab/diff/parallel_diff.rb
@@ -14,16 +14,7 @@ module Gitlab
         lines = []
         highlighted_diff_lines = diff_file.highlighted_diff_lines
         highlighted_diff_lines.each do |line|
-          if line.meta? || line.unchanged?
-            # line in the right panel is the same as in the left one
-            lines << {
-              left: line,
-              right: line
-            }
-
-            free_right_index = nil
-            i += 1
-          elsif line.removed?
+          if line.removed?
             lines << {
               left: line,
               right: nil
@@ -51,6 +42,15 @@ module Gitlab
               free_right_index = nil
               i += 1
             end
+          elsif line.meta? || line.unchanged?
+            # line in the right panel is the same as in the left one
+            lines << {
+              left: line,
+              right: line
+            }
+
+            free_right_index = nil
+            i += 1
           end
         end
 
diff --git a/lib/gitlab/downtime_check.rb b/lib/gitlab/downtime_check.rb
index ab9537ed7d701752ff9c020da8a45448c016a65a..941244694e2c028d6fb993417be9f68e215231ef 100644
--- a/lib/gitlab/downtime_check.rb
+++ b/lib/gitlab/downtime_check.rb
@@ -50,8 +50,8 @@ module Gitlab
 
     # Returns the class for the given migration file path.
     def class_for_migration_file(path)
-      File.basename(path, File.extname(path)).split('_', 2).last.camelize.
-        constantize
+      File.basename(path, File.extname(path)).split('_', 2).last.camelize
+        .constantize
     end
 
     # Returns true if the given migration can be performed without downtime.
diff --git a/lib/gitlab/ee_compat_check.rb b/lib/gitlab/ee_compat_check.rb
index 6d326ee213aa29ef23d0c610134bb942ac59c38c..1a5887dab7e636e99eabad349e576cb86a4c7478 100644
--- a/lib/gitlab/ee_compat_check.rb
+++ b/lib/gitlab/ee_compat_check.rb
@@ -76,9 +76,13 @@ module Gitlab
 
       step(
         "Generating the patch against origin/master in #{patch_path}",
-        %W[git diff --binary origin/master > #{patch_path}]
+        %w[git diff --binary origin/master...HEAD]
       ) do |output, status|
-        throw(:halt_check, :ko) unless status.zero? && File.exist?(patch_path)
+        throw(:halt_check, :ko) unless status.zero?
+
+        File.write(patch_path, output)
+
+        throw(:halt_check, :ko) unless File.exist?(patch_path)
       end
     end
 
@@ -130,7 +134,15 @@ module Gitlab
       step("Fetching CE/#{ce_branch}", %W[git fetch #{CE_REPO} #{ce_branch}])
       step(
         "Checking if #{patch_path} applies cleanly to EE/master",
-        %W[git apply --check --3way #{patch_path}]
+        # Don't use --check here because it can result in a 0-exit status even
+        # though the patch doesn't apply cleanly, e.g.:
+        #   > git apply --check --3way foo.patch
+        #   error: patch failed: lib/gitlab/ee_compat_check.rb:74
+        #   Falling back to three-way merge...
+        #   Applied patch to 'lib/gitlab/ee_compat_check.rb' with conflicts.
+        #   > echo $?
+        #   0
+        %W[git apply --3way #{patch_path}]
       ) do |output, status|
         puts output
         unless status.zero?
@@ -145,6 +157,7 @@ module Gitlab
           status = 0 if failed_files.empty?
         end
 
+        command(%w[git reset --hard])
         status
       end
     end
@@ -292,7 +305,7 @@ module Gitlab
 
           # In the CE repo
           $ git fetch origin master
-          $ git diff --binary origin/master > #{ce_branch}.patch
+          $ git diff --binary origin/master...HEAD -- > #{ce_branch}.patch
 
           # In the EE repo
           $ git fetch origin master
diff --git a/lib/gitlab/email/html_parser.rb b/lib/gitlab/email/html_parser.rb
index a4ca62bfc411937e45afbf94c1342bc9a57f64c0..50559a489734a72f1355c5eb3a6d46e86a259d46 100644
--- a/lib/gitlab/email/html_parser.rb
+++ b/lib/gitlab/email/html_parser.rb
@@ -17,6 +17,13 @@ module Gitlab
       def filter_replies!
         document.xpath('//blockquote').each(&:remove)
         document.xpath('//table').each(&:remove)
+
+        # bogus links with no href are sometimes added by outlook,
+        # and can result in Html2Text adding extra square brackets
+        # to the text, so we unwrap them here.
+        document.xpath('//a[not(@href)]').each do |link|
+          link.replace(link.children)
+        end
       end
 
       def filtered_html
diff --git a/lib/gitlab/email/message/repository_push.rb b/lib/gitlab/email/message/repository_push.rb
index ea035e33eff8a262f10d6f2917a57863932c7208..42fc2a4ea194aa4beea8410d88bf03964fcab9f4 100644
--- a/lib/gitlab/email/message/repository_push.rb
+++ b/lib/gitlab/email/message/repository_push.rb
@@ -96,20 +96,13 @@ module Gitlab
         def target_url
           if @action == :push && commits
             if commits.length > 1
-              namespace_project_compare_url(project_namespace,
-                                            project,
-                                            from: compare.start_commit,
-                                            to:   compare.head_commit)
+              project_compare_url(project, from: compare.start_commit, to: compare.head_commit)
             else
-              namespace_project_commit_url(project_namespace,
-                                           project,
-                                           commits.first)
+              project_commit_url(project, commits.first)
             end
           else
             unless @action == :delete
-              namespace_project_tree_url(project_namespace,
-                                         project,
-                                         ref_name)
+              project_tree_url(project, ref_name)
             end
           end
         end
diff --git a/lib/gitlab/exclusive_lease.rb b/lib/gitlab/exclusive_lease.rb
index 62ddd45785d90780a677957c8efc957bc466464e..a0f46594eb1cb460a6f627c474c12fd784577eb5 100644
--- a/lib/gitlab/exclusive_lease.rb
+++ b/lib/gitlab/exclusive_lease.rb
@@ -10,13 +10,21 @@ module Gitlab
   # ExclusiveLease.
   #
   class ExclusiveLease
-    LUA_CANCEL_SCRIPT = <<-EOS.freeze
+    LUA_CANCEL_SCRIPT = <<~EOS.freeze
       local key, uuid = KEYS[1], ARGV[1]
       if redis.call("get", key) == uuid then
         redis.call("del", key)
       end
     EOS
 
+    LUA_RENEW_SCRIPT = <<~EOS.freeze
+      local key, uuid, ttl = KEYS[1], ARGV[1], ARGV[2]
+      if redis.call("get", key) == uuid then
+        redis.call("expire", key, ttl)
+        return uuid
+      end
+    EOS
+
     def self.cancel(key, uuid)
       Gitlab::Redis.with do |redis|
         redis.eval(LUA_CANCEL_SCRIPT, keys: [redis_key(key)], argv: [uuid])
@@ -42,6 +50,15 @@ module Gitlab
       end
     end
 
+    # Try to renew an existing lease. Return lease UUID on success,
+    # false if the lease is taken by a different UUID or inexistent.
+    def renew
+      Gitlab::Redis.with do |redis|
+        result = redis.eval(LUA_RENEW_SCRIPT, keys: [@redis_key], argv: [@uuid, @timeout])
+        result == @uuid
+      end
+    end
+
     # Returns true if the key for this lease is set.
     def exists?
       Gitlab::Redis.with do |redis|
diff --git a/lib/gitlab/fake_application_settings.rb b/lib/gitlab/fake_application_settings.rb
new file mode 100644
index 0000000000000000000000000000000000000000..bb14a8cd9e7965679e4547a4ae16d2f7a40f54dc
--- /dev/null
+++ b/lib/gitlab/fake_application_settings.rb
@@ -0,0 +1,27 @@
+# This class extends an OpenStruct object by adding predicate methods to mimic
+# ActiveRecord access. We rely on the initial values being true or false to
+# determine whether to define a predicate method because for a newly-added
+# column that has not been migrated yet, there is no way to determine the
+# column type without parsing db/schema.rb.
+module Gitlab
+  class FakeApplicationSettings < OpenStruct
+    def initialize(options = {})
+      super
+
+      FakeApplicationSettings.define_predicate_methods(options)
+    end
+
+    # Mimic ActiveRecord predicate methods for boolean values
+    def self.define_predicate_methods(options)
+      options.each do |key, value|
+        next if key.to_s.end_with?('?')
+        next unless [true, false].include?(value)
+
+        define_method "#{key}?" do
+          actual_key = key.to_s.chomp('?')
+          self[actual_key]
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb
index 936606152e9ac7b5c0ea31f4d873a207e7af187f..4175746be3935fafb55a5521057f8c8b37821840 100644
--- a/lib/gitlab/git.rb
+++ b/lib/gitlab/git.rb
@@ -7,8 +7,10 @@ module Gitlab
     CommandError = Class.new(StandardError)
 
     class << self
+      include Gitlab::EncodingHelper
+
       def ref_name(ref)
-        ref.sub(/\Arefs\/(tags|heads)\//, '')
+        encode! ref.sub(/\Arefs\/(tags|heads)\//, '')
       end
 
       def branch_name(ref)
diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb
index 33a7624e3034b0e9925ca0e1511a4cd48e8d5bb4..ffe4f3ca95f6fcd83a0fe658890a55469b2dc38f 100644
--- a/lib/gitlab/git/blob.rb
+++ b/lib/gitlab/git/blob.rb
@@ -14,6 +14,51 @@ module Gitlab
 
       class << self
         def find(repository, sha, path)
+          Gitlab::GitalyClient.migrate(:project_raw_show) do |is_enabled|
+            if is_enabled
+              find_by_gitaly(repository, sha, path)
+            else
+              find_by_rugged(repository, sha, path)
+            end
+          end
+        end
+
+        def find_by_gitaly(repository, sha, path)
+          path = path.sub(/\A\/*/, '')
+          path = '/' if path.empty?
+          name = File.basename(path)
+          entry = Gitlab::GitalyClient::Commit.new(repository).tree_entry(sha, path, MAX_DATA_DISPLAY_SIZE)
+          return unless entry
+
+          case entry.type
+          when :COMMIT
+            new(
+              id: entry.oid,
+              name: name,
+              size: 0,
+              data: '',
+              path: path,
+              commit_id: sha
+            )
+          when :BLOB
+            # EncodingDetector checks the first 1024 * 1024 bytes for NUL byte, libgit2 checks
+            # only the first 8000 (https://github.com/libgit2/libgit2/blob/2ed855a9e8f9af211e7274021c2264e600c0f86b/src/filter.h#L15),
+            # which is what we use below to keep a consistent behavior.
+            detect = CharlockHolmes::EncodingDetector.new(8000).detect(entry.data)
+            new(
+              id: entry.oid,
+              name: name,
+              size: entry.size,
+              data: entry.data.dup,
+              mode: entry.mode.to_s(8),
+              path: path,
+              commit_id: sha,
+              binary: detect && detect[:type] == :binary
+            )
+          end
+        end
+
+        def find_by_rugged(repository, sha, path)
           commit = repository.lookup(sha)
           root_tree = commit.tree
 
@@ -130,6 +175,10 @@ module Gitlab
         encode! @name
       end
 
+      def path
+        encode! @path
+      end
+
       def truncated?
         size && (size > loaded_size)
       end
diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb
index bb04731f08cbd787a18794c7fab8a689ef76ada5..9c0606d780a0311ff2bbe1031bbc91c901f01fa0 100644
--- a/lib/gitlab/git/commit.rb
+++ b/lib/gitlab/git/commit.rb
@@ -4,7 +4,7 @@ module Gitlab
     class Commit
       include Gitlab::EncodingHelper
 
-      attr_accessor :raw_commit, :head, :refs
+      attr_accessor :raw_commit, :head
 
       SERIALIZE_KEYS = [
         :id, :message, :parent_ids,
@@ -104,9 +104,63 @@ module Gitlab
           []
         end
 
-        # Delegate Repository#find_commits
+        # Returns commits collection
+        #
+        # Ex.
+        #   Commit.find_all(
+        #     repo,
+        #     ref: 'master',
+        #     max_count: 10,
+        #     skip: 5,
+        #     order: :date
+        #   )
+        #
+        #   +options+ is a Hash of optional arguments to git
+        #     :ref is the ref from which to begin (SHA1 or name)
+        #     :max_count is the maximum number of commits to fetch
+        #     :skip is the number of commits to skip
+        #     :order is the commits order and allowed value is :none (default), :date,
+        #        :topo, or any combination of them (in an array). Commit ordering types
+        #        are documented here:
+        #        http://www.rubydoc.info/github/libgit2/rugged/Rugged#SORT_NONE-constant)
+        #
         def find_all(repo, options = {})
-          repo.find_commits(options)
+          actual_options = options.dup
+
+          allowed_options = [:ref, :max_count, :skip, :order]
+
+          actual_options.keep_if do |key|
+            allowed_options.include?(key)
+          end
+
+          default_options = { skip: 0 }
+          actual_options = default_options.merge(actual_options)
+
+          rugged = repo.rugged
+          walker = Rugged::Walker.new(rugged)
+
+          if actual_options[:ref]
+            walker.push(rugged.rev_parse_oid(actual_options[:ref]))
+          else
+            rugged.references.each("refs/heads/*") do |ref|
+              walker.push(ref.target_id)
+            end
+          end
+
+          walker.sorting(rugged_sort_type(actual_options[:order]))
+
+          commits = []
+          offset = actual_options[:skip]
+          limit = actual_options[:max_count]
+          walker.each(offset: offset, limit: limit) do |commit|
+            commits.push(decorate(commit))
+          end
+
+          walker.reset
+
+          commits
+        rescue Rugged::OdbError
+          []
         end
 
         def decorate(commit, ref = nil)
@@ -131,6 +185,20 @@ module Gitlab
           diff.find_similar!(break_rewrites: break_rewrites)
           diff
         end
+
+        # Returns the `Rugged` sorting type constant for one or more given
+        # sort types. Valid keys are `:none`, `:topo`, and `:date`, or an array
+        # containing more than one of them. `:date` uses a combination of date and
+        # topological sorting to closer mimic git's native ordering.
+        def rugged_sort_type(sort_type)
+          @rugged_sort_types ||= {
+            none: Rugged::SORT_NONE,
+            topo: Rugged::SORT_TOPO,
+            date: Rugged::SORT_DATE | Rugged::SORT_TOPO
+          }
+
+          @rugged_sort_types.fetch(sort_type, Rugged::SORT_NONE)
+        end
       end
 
       def initialize(raw_commit, head = nil)
@@ -175,8 +243,8 @@ module Gitlab
       # Shows the diff between the commit's parent and the commit.
       #
       # Cuts out the header and stats from #to_patch and returns only the diff.
-      def to_diff(options = {})
-        diff_from_parent(options).patch
+      def to_diff
+        diff_from_parent.patch
       end
 
       # Returns a diff object for the changes from this commit's first parent.
diff --git a/lib/gitlab/git/diff.rb b/lib/gitlab/git/diff.rb
index 4b689f0e94f649e429f693a66960bf718486151c..cf7829a583bb355b1bfd8b088faa260231d2dfef 100644
--- a/lib/gitlab/git/diff.rb
+++ b/lib/gitlab/git/diff.rb
@@ -16,11 +16,11 @@ module Gitlab
       alias_method :renamed_file?, :renamed_file
 
       attr_accessor :expanded
+      attr_writer :too_large
 
       alias_method :expanded?, :expanded
 
-      # We need this accessor because of `to_hash` and `init_from_hash`
-      attr_accessor :too_large
+      SERIALIZE_KEYS = %i(diff new_path old_path a_mode b_mode new_file renamed_file deleted_file too_large).freeze
 
       class << self
         # The maximum size of a diff to display.
@@ -231,16 +231,10 @@ module Gitlab
         end
       end
 
-      def serialize_keys
-        @serialize_keys ||= %i(diff new_path old_path a_mode b_mode new_file renamed_file deleted_file too_large)
-      end
-
       def to_hash
         hash = {}
 
-        keys = serialize_keys
-
-        keys.each do |key|
+        SERIALIZE_KEYS.each do |key|
           hash[key] = send(key)
         end
 
@@ -267,6 +261,9 @@ module Gitlab
         end
       end
 
+      # This is used by `to_hash` and `init_from_hash`.
+      alias_method :too_large, :too_large?
+
       def too_large!
         @diff = ''
         @line_count = 0
@@ -315,13 +312,13 @@ module Gitlab
       def init_from_hash(hash)
         raw_diff = hash.symbolize_keys
 
-        serialize_keys.each do |key|
+        SERIALIZE_KEYS.each do |key|
           send(:"#{key}=", raw_diff[key.to_sym])
         end
       end
 
       def init_from_gitaly(diff)
-        @diff = diff.patch if diff.respond_to?(:patch)
+        @diff = encode!(diff.patch) if diff.respond_to?(:patch)
         @new_path = encode!(diff.to_path.dup)
         @old_path = encode!(diff.from_path.dup)
         @a_mode = diff.old_mode.to_s(8)
diff --git a/lib/gitlab/git/gitmodules_parser.rb b/lib/gitlab/git/gitmodules_parser.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f4e3b5e5129de20fbc8481d481301dcad65bdbc0
--- /dev/null
+++ b/lib/gitlab/git/gitmodules_parser.rb
@@ -0,0 +1,77 @@
+module Gitlab
+  module Git
+    class GitmodulesParser
+      def initialize(content)
+        @content = content
+      end
+
+      # Parses the contents of a .gitmodules file and returns a hash of
+      # submodule information, indexed by path.
+      def parse
+        reindex_by_path(get_submodules_by_name)
+      end
+
+      private
+
+      class State
+        def initialize
+          @result = {}
+          @current_submodule = nil
+        end
+
+        def start_section(section)
+          # In some .gitmodules files (e.g. nodegit's), a header
+          # with the same name appears multiple times; we want to
+          # accumulate the configs across these
+          @current_submodule = @result[section] || { 'name' => section }
+          @result[section] = @current_submodule
+        end
+
+        def set_attribute(attr, value)
+          @current_submodule[attr] = value
+        end
+
+        def section_started?
+          !@current_submodule.nil?
+        end
+
+        def submodules_by_name
+          @result
+        end
+      end
+
+      def get_submodules_by_name
+        iterator = State.new
+
+        @content.split("\n").each_with_object(iterator) do |text, iterator|
+          next if text =~ /^\s*#/
+
+          if text =~ /\A\[submodule "(?<name>[^"]+)"\]\z/
+            iterator.start_section($~[:name])
+          else
+            next unless iterator.section_started?
+
+            next unless text =~ /\A\s*(?<key>\w+)\s*=\s*(?<value>.*)\z/
+
+            value = $~[:value].chomp
+            iterator.set_attribute($~[:key], value)
+          end
+        end
+
+        iterator.submodules_by_name
+      end
+
+      def reindex_by_path(submodules_by_name)
+        # Convert from an indexed by name to an array indexed by path
+        # If a submodule doesn't have a path, it is considered bogus
+        # and is ignored
+        submodules_by_name.each_with_object({}) do |(name, data), results|
+          path = data.delete 'path'
+          next unless path
+
+          results[path] = data
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/git/hook.rb b/lib/gitlab/git/hook.rb
index bd90d24a2ecd333477cf96e530215a935f562f39..5042916343be4ae2f1e53d2ffe67982479cb39e8 100644
--- a/lib/gitlab/git/hook.rb
+++ b/lib/gitlab/git/hook.rb
@@ -4,9 +4,10 @@ module Gitlab
       GL_PROTOCOL = 'web'.freeze
       attr_reader :name, :repo_path, :path
 
-      def initialize(name, repo_path)
+      def initialize(name, project)
         @name = name
-        @repo_path = repo_path
+        @project = project
+        @repo_path = project.repository.path
         @path = File.join(repo_path.strip, 'hooks', name)
       end
 
@@ -38,7 +39,8 @@ module Gitlab
         vars = {
           'GL_ID' => gl_id,
           'PWD' => repo_path,
-          'GL_PROTOCOL' => GL_PROTOCOL
+          'GL_PROTOCOL' => GL_PROTOCOL,
+          'GL_REPOSITORY' => Gitlab::GlRepository.gl_repository(@project, false)
         }
 
         options = {
diff --git a/lib/gitlab/git/index.rb b/lib/gitlab/git/index.rb
index 1add037fa5f154b6da583f839571458aae015314..666743006e54ee5a17d688604073306516ac58b0 100644
--- a/lib/gitlab/git/index.rb
+++ b/lib/gitlab/git/index.rb
@@ -110,10 +110,6 @@ module Gitlab
           if segment == '..'
             raise IndexError, 'Path cannot include directory traversal'
           end
-
-          unless segment =~ Gitlab::Regex.file_name_regex
-            raise IndexError, "Path #{Gitlab::Regex.file_name_regex_message}"
-          end
         end
 
         pathname.to_s
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 85695d0a4df9fcf407bd441850e8a32717ea2cdb..dd5a4d5ad5568747f0cfead969abe4691386ce2b 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -113,9 +113,7 @@ module Gitlab
       def local_branches(sort_by: nil)
         gitaly_migrate(:local_branches) do |is_enabled|
           if is_enabled
-            gitaly_ref_client.local_branches(sort_by: sort_by).map do |gitaly_branch|
-              Gitlab::Git::Branch.new(self, gitaly_branch.name, gitaly_branch)
-            end
+            gitaly_ref_client.local_branches(sort_by: sort_by)
           else
             branches(filter: :local, sort_by: sort_by)
           end
@@ -494,70 +492,6 @@ module Gitlab
         end
       end
 
-      # Returns commits collection
-      #
-      # Ex.
-      #   repo.find_commits(
-      #     ref: 'master',
-      #     max_count: 10,
-      #     skip: 5,
-      #     order: :date
-      #   )
-      #
-      #   +options+ is a Hash of optional arguments to git
-      #     :ref is the ref from which to begin (SHA1 or name)
-      #     :contains is the commit contained by the refs from which to begin (SHA1 or name)
-      #     :max_count is the maximum number of commits to fetch
-      #     :skip is the number of commits to skip
-      #     :order is the commits order and allowed value is :none (default), :date,
-      #        :topo, or any combination of them (in an array). Commit ordering types
-      #        are documented here:
-      #        http://www.rubydoc.info/github/libgit2/rugged/Rugged#SORT_NONE-constant)
-      #
-      def find_commits(options = {})
-        actual_options = options.dup
-
-        allowed_options = [:ref, :max_count, :skip, :contains, :order]
-
-        actual_options.keep_if do |key|
-          allowed_options.include?(key)
-        end
-
-        default_options = { skip: 0 }
-        actual_options = default_options.merge(actual_options)
-
-        walker = Rugged::Walker.new(rugged)
-
-        if actual_options[:ref]
-          walker.push(rugged.rev_parse_oid(actual_options[:ref]))
-        elsif actual_options[:contains]
-          branches_contains(actual_options[:contains]).each do |branch|
-            walker.push(branch.target_id)
-          end
-        else
-          rugged.references.each("refs/heads/*") do |ref|
-            walker.push(ref.target_id)
-          end
-        end
-
-        sort_type = rugged_sort_type(actual_options[:order])
-        walker.sorting(sort_type)
-
-        commits = []
-        offset = actual_options[:skip]
-        limit = actual_options[:max_count]
-        walker.each(offset: offset, limit: limit) do |commit|
-          gitlab_commit = Gitlab::Git::Commit.decorate(commit)
-          commits.push(gitlab_commit)
-        end
-
-        walker.reset
-
-        commits
-      rescue Rugged::OdbError
-        []
-      end
-
       # Returns branch names collection that contains the special commit(SHA1
       # or name)
       #
@@ -613,31 +547,23 @@ module Gitlab
         rugged.rev_parse(oid_or_ref_name)
       end
 
-      # Return hash with submodules info for this repository
+      # Returns url for submodule
       #
       # Ex.
-      #   {
-      #     "rack"  => {
-      #       "id" => "c67be4624545b4263184c4a0e8f887efd0a66320",
-      #       "path" => "rack",
-      #       "url" => "git://github.com/chneukirchen/rack.git"
-      #     },
-      #     "encoding" => {
-      #       "id" => ....
-      #     }
-      #   }
+      #   @repository.submodule_url_for('master', 'rack')
+      #   # => git@localhost:rack.git
       #
-      def submodules(ref)
-        commit = rev_parse_target(ref)
-        return {} unless commit
-
-        begin
-          content = blob_content(commit, ".gitmodules")
-        rescue InvalidBlobName
-          return {}
+      def submodule_url_for(ref, path)
+        Gitlab::GitalyClient.migrate(:submodule_url_for) do |is_enabled|
+          if is_enabled
+            gitaly_submodule_url_for(ref, path)
+          else
+            if submodules(ref).any?
+              submodule = submodules(ref)[path]
+              submodule['url'] if submodule
+            end
+          end
         end
-
-        parse_gitmodules(commit, content)
       end
 
       # Return total commits count accessible from passed ref
@@ -975,6 +901,35 @@ module Gitlab
 
       private
 
+      # We are trying to deprecate this method because it does a lot of work
+      # but it seems to be used only to look up submodule URL's.
+      # https://gitlab.com/gitlab-org/gitaly/issues/329
+      def submodules(ref)
+        commit = rev_parse_target(ref)
+        return {} unless commit
+
+        begin
+          content = blob_content(commit, ".gitmodules")
+        rescue InvalidBlobName
+          return {}
+        end
+
+        parser = GitmodulesParser.new(content)
+        fill_submodule_ids(commit, parser.parse)
+      end
+
+      def gitaly_submodule_url_for(ref, path)
+        # We don't care about the contents so 1 byte is enough. Can't request 0 bytes, 0 means unlimited.
+        commit_object = gitaly_commit_client.tree_entry(ref, path, 1)
+
+        return unless commit_object && commit_object.type == :COMMIT
+
+        gitmodules = gitaly_commit_client.tree_entry(ref, '.gitmodules', Blob::MAX_DATA_DISPLAY_SIZE)
+        found_module = GitmodulesParser.new(gitmodules.data).parse[path]
+
+        found_module && found_module['url']
+      end
+
       def alternate_object_directories
         Gitlab::Git::Env.all.values_at(*ALLOWED_OBJECT_DIRECTORIES_VARIABLES).compact
       end
@@ -998,42 +953,19 @@ module Gitlab
         end
       end
 
-      # Parses the contents of a .gitmodules file and returns a hash of
-      # submodule information.
-      def parse_gitmodules(commit, content)
-        modules = {}
-
-        name = nil
-        content.each_line do |line|
-          case line.strip
-          when /\A\[submodule "(?<name>[^"]+)"\]\z/ # Submodule header
-            name = $~[:name]
-            modules[name] = {}
-          when /\A(?<key>\w+)\s*=\s*(?<value>.*)\z/ # Key/value pair
-            key = $~[:key]
-            value = $~[:value].chomp
-
-            next unless name && modules[name]
-
-            modules[name][key] = value
-
-            if key == 'path'
-              begin
-                modules[name]['id'] = blob_content(commit, value)
-              rescue InvalidBlobName
-                # The current entry is invalid
-                modules.delete(name)
-                name = nil
-              end
-            end
-          when /\A#/ # Comment
-            next
-          else # Invalid line
-            name = nil
+      # Fill in the 'id' field of a submodule hash from its values
+      # as-of +commit+. Return a Hash consisting only of entries
+      # from the submodule hash for which the 'id' field is filled.
+      def fill_submodule_ids(commit, submodule_data)
+        submodule_data.each do |path, data|
+          id = begin
+            blob_content(commit, path)
+          rescue InvalidBlobName
+            nil
           end
+          data['id'] = id
         end
-
-        modules
+        submodule_data.select { |path, data| data['id'] }
       end
 
       # Returns true if +commit+ introduced changes to +path+, using commit
@@ -1250,20 +1182,6 @@ module Gitlab
       rescue GRPC::BadStatus => e
         raise CommandError.new(e)
       end
-
-      # Returns the `Rugged` sorting type constant for one or more given
-      # sort types. Valid keys are `:none`, `:topo`, and `:date`, or an array
-      # containing more than one of them. `:date` uses a combination of date and
-      # topological sorting to closer mimic git's native ordering.
-      def rugged_sort_type(sort_type)
-        @rugged_sort_types ||= {
-          none: Rugged::SORT_NONE,
-          topo: Rugged::SORT_TOPO,
-          date: Rugged::SORT_DATE | Rugged::SORT_TOPO
-        }
-
-        @rugged_sort_types.fetch(sort_type, Rugged::SORT_NONE)
-      end
     end
   end
 end
diff --git a/lib/gitlab/git/tree.rb b/lib/gitlab/git/tree.rb
index b9afa05c819a0d1e84e56c87c1c1289915b52a95..b6d4e6cfe46edc1247db025f6d6b69a546649ff7 100644
--- a/lib/gitlab/git/tree.rb
+++ b/lib/gitlab/git/tree.rb
@@ -80,6 +80,10 @@ module Gitlab
         encode! @name
       end
 
+      def path
+        encode! @path
+      end
+
       def dir?
         type == :tree
       end
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index 0a19d24eb2005c5a7fc216c0732c7f0b20213c36..0b62911958dfb0e4642149864af85917761c81a0 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -22,12 +22,13 @@ module Gitlab
     PUSH_COMMANDS = %w{ git-receive-pack }.freeze
     ALL_COMMANDS = DOWNLOAD_COMMANDS + PUSH_COMMANDS
 
-    attr_reader :actor, :project, :protocol, :authentication_abilities
+    attr_reader :actor, :project, :protocol, :authentication_abilities, :redirected_path
 
-    def initialize(actor, project, protocol, authentication_abilities:)
+    def initialize(actor, project, protocol, authentication_abilities:, redirected_path: nil)
       @actor    = actor
       @project  = project
       @protocol = protocol
+      @redirected_path = redirected_path
       @authentication_abilities = authentication_abilities
     end
 
@@ -35,6 +36,7 @@ module Gitlab
       check_protocol!
       check_active_user!
       check_project_accessibility!
+      check_project_moved!
       check_command_disabled!(cmd)
       check_command_existence!(cmd)
       check_repository_existence!
@@ -87,6 +89,21 @@ module Gitlab
       end
     end
 
+    def check_project_moved!
+      if redirected_path
+        url = protocol == 'ssh' ? project.ssh_url_to_repo : project.http_url_to_repo
+        message = <<-MESSAGE.strip_heredoc
+          Project '#{redirected_path}' was moved to '#{project.full_path}'.
+
+          Please update your Git remote and try again:
+
+            git remote set-url origin #{url}
+        MESSAGE
+
+        raise NotFoundError, message
+      end
+    end
+
     def check_command_disabled!(cmd)
       if upload_pack?(cmd)
         check_upload_pack_disabled!
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index 2343446bf223f39edede2aba17e5416fab5e8f87..f605c06dfc31527a5ce618245ac3a488744d2ee7 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -1,3 +1,5 @@
+require 'base64'
+
 require 'gitaly'
 
 module Gitlab
@@ -48,6 +50,26 @@ module Gitlab
       address
     end
 
+    # All Gitaly RPC call sites should use GitalyClient.call. This method
+    # makes sure that per-request authentication headers are set.
+    def self.call(storage, service, rpc, request)
+      metadata = request_metadata(storage)
+      metadata = yield(metadata) if block_given?
+      stub(service, storage).send(rpc, request, metadata)
+    end
+  
+    def self.request_metadata(storage)
+      encoded_token = Base64.strict_encode64(token(storage).to_s)
+      { metadata: { 'authorization' => "Bearer #{encoded_token}" } }
+    end
+
+    def self.token(storage)
+      params = Gitlab.config.repositories.storages[storage]
+      raise "storage not found: #{storage.inspect}" if params.nil?
+
+      params['gitaly_token'].presence || Gitlab.config.gitaly['token']
+    end
+
     def self.enabled?
       Gitlab.config.gitaly.enabled
     end
diff --git a/lib/gitlab/gitaly_client/commit.rb b/lib/gitlab/gitaly_client/commit.rb
index ba3da781dade7ed76e1da30600352a9b4544d8b9..b88776197974590ff8ab818dbfb903dc68c4fdf4 100644
--- a/lib/gitlab/gitaly_client/commit.rb
+++ b/lib/gitlab/gitaly_client/commit.rb
@@ -11,33 +11,51 @@ module Gitlab
       end
 
       def is_ancestor(ancestor_id, child_id)
-        stub = GitalyClient.stub(:commit, @repository.storage)
         request = Gitaly::CommitIsAncestorRequest.new(
           repository: @gitaly_repo,
           ancestor_id: ancestor_id,
           child_id: child_id
         )
 
-        stub.commit_is_ancestor(request).value
+        GitalyClient.call(@repository.storage, :commit, :commit_is_ancestor, request).value
       end
 
       def diff_from_parent(commit, options = {})
         request_params = commit_diff_request_params(commit, options)
         request_params[:ignore_whitespace_change] = options.fetch(:ignore_whitespace_change, false)
-
-        response = diff_service_stub.commit_diff(Gitaly::CommitDiffRequest.new(request_params))
+        request = Gitaly::CommitDiffRequest.new(request_params)
+        response = GitalyClient.call(@repository.storage, :diff, :commit_diff, request)
         Gitlab::Git::DiffCollection.new(GitalyClient::DiffStitcher.new(response), options)
       end
 
       def commit_deltas(commit)
-        request_params = commit_diff_request_params(commit)
-
-        response = diff_service_stub.commit_delta(Gitaly::CommitDeltaRequest.new(request_params))
+        request = Gitaly::CommitDeltaRequest.new(commit_diff_request_params(commit))
+        response = GitalyClient.call(@repository.storage, :diff, :commit_delta, request)
         response.flat_map do |msg|
           msg.deltas.map { |d| Gitlab::Git::Diff.new(d) }
         end
       end
 
+      def tree_entry(ref, path, limit = nil)
+        request = Gitaly::TreeEntryRequest.new(
+          repository: @gitaly_repo,
+          revision: ref,
+          path: path.dup.force_encoding(Encoding::ASCII_8BIT),
+          limit: limit.to_i
+        )
+
+        response = GitalyClient.call(@repository.storage, :commit, :tree_entry, request)
+        entry = response.first
+        return unless entry.oid.present?
+
+        if entry.type == :BLOB
+          rest_of_data = response.reduce("") { |memo, msg| memo << msg.data }
+          entry.data += rest_of_data
+        end
+
+        entry
+      end
+
       private
 
       def commit_diff_request_params(commit, options = {})
@@ -50,10 +68,6 @@ module Gitlab
           paths: options.fetch(:paths, [])
         }
       end
-
-      def diff_service_stub
-        GitalyClient.stub(:diff, @repository.storage)
-      end
     end
   end
 end
diff --git a/lib/gitlab/gitaly_client/diff_stitcher.rb b/lib/gitlab/gitaly_client/diff_stitcher.rb
index d84e8d752dc36a77ef70e3d73652cbaa023b8a3b..65d81dc5d4603b41ba0769385fb977753c98321f 100644
--- a/lib/gitlab/gitaly_client/diff_stitcher.rb
+++ b/lib/gitlab/gitaly_client/diff_stitcher.rb
@@ -13,7 +13,10 @@ module Gitlab
         @rpc_response.each do |diff_msg|
           if current_diff.nil?
             diff_params = diff_msg.to_h.slice(*GitalyClient::Diff::FIELDS)
-            diff_params[:patch] = diff_msg.raw_patch_data
+            # gRPC uses frozen strings by default, and we need to have an unfrozen string as it
+            # gets processed further down the line. So we unfreeze the first chunk of the patch
+            # in case it's the only chunk we receive for this diff.
+            diff_params[:patch] = diff_msg.raw_patch_data.dup
 
             current_diff = GitalyClient::Diff.new(diff_params)
           else
diff --git a/lib/gitlab/gitaly_client/notifications.rb b/lib/gitlab/gitaly_client/notifications.rb
index 719554eac52772acc12ed1fd3ad00f4526f7073a..78ed433e6b8658ad2faa38522b71bd3fc95183e4 100644
--- a/lib/gitlab/gitaly_client/notifications.rb
+++ b/lib/gitlab/gitaly_client/notifications.rb
@@ -1,17 +1,19 @@
 module Gitlab
   module GitalyClient
     class Notifications
-      attr_accessor :stub
-
       # 'repository' is a Gitlab::Git::Repository
       def initialize(repository)
         @gitaly_repo = repository.gitaly_repository
-        @stub = GitalyClient.stub(:notifications, repository.storage)
+        @storage = repository.storage
       end
 
       def post_receive
-        request = Gitaly::PostReceiveRequest.new(repository: @gitaly_repo)
-        @stub.post_receive(request)
+        GitalyClient.call(
+          @storage,
+          :notifications,
+          :post_receive,
+          Gitaly::PostReceiveRequest.new(repository: @gitaly_repo)
+        )
       end
     end
   end
diff --git a/lib/gitlab/gitaly_client/ref.rb b/lib/gitlab/gitaly_client/ref.rb
index 227fe45642ecca1dc96cf19e048a46c8a6615991..6edc69de078882a8d093c2f842d13272d2a331e1 100644
--- a/lib/gitlab/gitaly_client/ref.rb
+++ b/lib/gitlab/gitaly_client/ref.rb
@@ -1,29 +1,31 @@
 module Gitlab
   module GitalyClient
     class Ref
-      attr_accessor :stub
+      include Gitlab::EncodingHelper
 
       # 'repository' is a Gitlab::Git::Repository
       def initialize(repository)
+        @repository = repository
         @gitaly_repo = repository.gitaly_repository
-        @stub = GitalyClient.stub(:ref, repository.storage)
+        @storage = repository.storage
       end
 
       def default_branch_name
         request = Gitaly::FindDefaultBranchNameRequest.new(repository: @gitaly_repo)
-        branch_name = stub.find_default_branch_name(request).name
-
-        Gitlab::Git.branch_name(branch_name)
+        response = GitalyClient.call(@storage, :ref, :find_default_branch_name, request)
+        Gitlab::Git.branch_name(response.name)
       end
 
       def branch_names
         request = Gitaly::FindAllBranchNamesRequest.new(repository: @gitaly_repo)
-        consume_refs_response(stub.find_all_branch_names(request), prefix: 'refs/heads/')
+        response = GitalyClient.call(@storage, :ref, :find_all_branch_names, request)
+        consume_refs_response(response) { |name| Gitlab::Git.branch_name(name) }
       end
 
       def tag_names
         request = Gitaly::FindAllTagNamesRequest.new(repository: @gitaly_repo)
-        consume_refs_response(stub.find_all_tag_names(request), prefix: 'refs/tags/')
+        response = GitalyClient.call(@storage, :ref, :find_all_tag_names, request)
+        consume_refs_response(response) { |name| Gitlab::Git.tag_name(name) }
       end
 
       def find_ref_name(commit_id, ref_prefix)
@@ -32,8 +34,7 @@ module Gitlab
           commit_id: commit_id,
           prefix: ref_prefix
         )
-
-        stub.find_ref_name(request).name
+        encode!(GitalyClient.call(@storage, :ref, :find_ref_name, request).name.dup)
       end
 
       def count_tag_names
@@ -47,25 +48,34 @@ module Gitlab
       def local_branches(sort_by: nil)
         request = Gitaly::FindLocalBranchesRequest.new(repository: @gitaly_repo)
         request.sort_by = sort_by_param(sort_by) if sort_by
-        consume_branches_response(stub.find_local_branches(request))
+        response = GitalyClient.call(@storage, :ref, :find_local_branches, request)
+        consume_branches_response(response)
       end
 
       private
 
-      def consume_refs_response(response, prefix:)
-        response.flat_map do |r|
-          r.names.map { |name| name.sub(/\A#{Regexp.escape(prefix)}/, '') }
-        end
+      def consume_refs_response(response)
+        response.flat_map { |message| message.names.map { |name| yield(name) } }
       end
 
       def sort_by_param(sort_by)
+        sort_by = 'name' if sort_by == 'name_asc'
+
         enum_value = Gitaly::FindLocalBranchesRequest::SortBy.resolve(sort_by.upcase.to_sym)
         raise ArgumentError, "Invalid sort_by key `#{sort_by}`" unless enum_value
         enum_value
       end
 
       def consume_branches_response(response)
-        response.flat_map { |r| r.branches }
+        response.flat_map do |message|
+          message.branches.map do |gitaly_branch|
+            Gitlab::Git::Branch.new(
+              @repository,
+              encode!(gitaly_branch.name.dup),
+              gitaly_branch.commit_id
+            )
+          end
+        end
       end
     end
   end
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index 319633656fff63ff8596f85b28a0210d85b2dda4..2d1ae6a5925de90a6383b3317c16d35c97b1a48e 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -2,11 +2,14 @@
 
 module Gitlab
   module GonHelper
+    include WebpackHelper
+
     def add_gon_variables
       gon.api_version            = 'v4'
       gon.default_avatar_url     = URI.join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s
       gon.max_file_size          = current_application_settings.max_attachment_size
       gon.asset_host             = ActionController::Base.asset_host
+      gon.webpack_public_path    = webpack_public_path
       gon.relative_url_root      = Gitlab.config.gitlab.relative_url_root
       gon.shortcuts_path         = help_page_path('shortcuts')
       gon.user_color_scheme      = Gitlab::ColorSchemes.for_user(current_user).css_class
diff --git a/lib/gitlab/group_hierarchy.rb b/lib/gitlab/group_hierarchy.rb
index e9d5d52cabb3cea76669c642648480574fe76832..5a31e56cb3077e2de9b10d775abf86731a3d1d70 100644
--- a/lib/gitlab/group_hierarchy.rb
+++ b/lib/gitlab/group_hierarchy.rb
@@ -3,33 +3,38 @@ module Gitlab
   #
   # This class uses recursive CTEs and as a result will only work on PostgreSQL.
   class GroupHierarchy
-    attr_reader :base, :model
-
-    # base - An instance of ActiveRecord::Relation for which to get parent or
-    #        child groups.
-    def initialize(base)
-      @base = base
-      @model = base.model
+    attr_reader :ancestors_base, :descendants_base, :model
+
+    # ancestors_base - An instance of ActiveRecord::Relation for which to
+    #                  get parent groups.
+    # descendants_base - An instance of ActiveRecord::Relation for which to
+    #                    get child groups. If omitted, ancestors_base is used.
+    def initialize(ancestors_base, descendants_base = ancestors_base)
+      raise ArgumentError.new("Model of ancestors_base does not match model of descendants_base") if ancestors_base.model != descendants_base.model
+
+      @ancestors_base = ancestors_base
+      @descendants_base = descendants_base
+      @model = ancestors_base.model
     end
 
-    # Returns a relation that includes the base set of groups and all their
-    # ancestors (recursively).
+    # Returns a relation that includes the ancestors_base set of groups
+    # and all their ancestors (recursively).
     def base_and_ancestors
-      return model.none unless Group.supports_nested_groups?
+      return ancestors_base unless Group.supports_nested_groups?
 
       base_and_ancestors_cte.apply_to(model.all)
     end
 
-    # Returns a relation that includes the base set of groups and all their
-    # descendants (recursively).
+    # Returns a relation that includes the descendants_base set of groups
+    # and all their descendants (recursively).
     def base_and_descendants
-      return model.none unless Group.supports_nested_groups?
+      return descendants_base unless Group.supports_nested_groups?
 
       base_and_descendants_cte.apply_to(model.all)
     end
 
-    # Returns a relation that includes the base groups, their ancestors, and the
-    # descendants of the base groups.
+    # Returns a relation that includes the base groups, their ancestors,
+    # and the descendants of the base groups.
     #
     # The resulting query will roughly look like the following:
     #
@@ -48,8 +53,10 @@ module Gitlab
     #
     # Using this approach allows us to further add criteria to the relation with
     # Rails thinking it's selecting data the usual way.
+    #
+    # If nested groups are not supported, ancestors_base is returned.
     def all_groups
-      return base unless Group.supports_nested_groups?
+      return ancestors_base unless Group.supports_nested_groups?
 
       ancestors = base_and_ancestors_cte
       descendants = base_and_descendants_cte
@@ -60,11 +67,11 @@ module Gitlab
       union = SQL::Union.new([model.unscoped.from(ancestors_table),
                               model.unscoped.from(descendants_table)])
 
-      model.
-        unscoped.
-        with.
-        recursive(ancestors.to_arel, descendants.to_arel).
-        from("(#{union.to_sql}) #{model.table_name}")
+      model
+        .unscoped
+        .with
+        .recursive(ancestors.to_arel, descendants.to_arel)
+        .from("(#{union.to_sql}) #{model.table_name}")
     end
 
     private
@@ -72,13 +79,13 @@ module Gitlab
     def base_and_ancestors_cte
       cte = SQL::RecursiveCTE.new(:base_and_ancestors)
 
-      cte << base.except(:order)
+      cte << ancestors_base.except(:order)
 
       # Recursively get all the ancestors of the base set.
-      cte << model.
-        from([groups_table, cte.table]).
-        where(groups_table[:id].eq(cte.table[:parent_id])).
-        except(:order)
+      cte << model
+        .from([groups_table, cte.table])
+        .where(groups_table[:id].eq(cte.table[:parent_id]))
+        .except(:order)
 
       cte
     end
@@ -86,13 +93,13 @@ module Gitlab
     def base_and_descendants_cte
       cte = SQL::RecursiveCTE.new(:base_and_descendants)
 
-      cte << base.except(:order)
+      cte << descendants_base.except(:order)
 
       # Recursively get all the descendants of the base set.
-      cte << model.
-        from([groups_table, cte.table]).
-        where(groups_table[:parent_id].eq(cte.table[:id])).
-        except(:order)
+      cte << model
+        .from([groups_table, cte.table])
+        .where(groups_table[:parent_id].eq(cte.table[:id]))
+        .except(:order)
 
       cte
     end
diff --git a/lib/gitlab/health_checks/fs_shards_check.rb b/lib/gitlab/health_checks/fs_shards_check.rb
index e78b7f22e0374ed8a43518eb2c0ebae1efa7749b..70da4080cae67bce6d3cb0bb4c299cb7edf45007 100644
--- a/lib/gitlab/health_checks/fs_shards_check.rb
+++ b/lib/gitlab/health_checks/fs_shards_check.rb
@@ -52,7 +52,7 @@ module Gitlab
             ]
           end
         rescue RuntimeError => ex
-          Rails.logger("unexpected error #{ex} when checking #{ok_metric}")
+          Rails.logger.error("unexpected error #{ex} when checking #{ok_metric}")
           [metric(ok_metric, 0, **labels)]
         end
 
diff --git a/lib/gitlab/highlight.rb b/lib/gitlab/highlight.rb
index 6b24da030dff01665a39605b0e340d4ed179b5a6..5408a1a68384ae98f3770885d1bec05dfa69f0d4 100644
--- a/lib/gitlab/highlight.rb
+++ b/lib/gitlab/highlight.rb
@@ -1,8 +1,8 @@
 module Gitlab
   class Highlight
     def self.highlight(blob_name, blob_content, repository: nil, plain: false)
-      new(blob_name, blob_content, repository: repository).
-        highlight(blob_content, continue: false, plain: plain)
+      new(blob_name, blob_content, repository: repository)
+        .highlight(blob_content, continue: false, plain: plain)
     end
 
     attr_reader :blob_name
diff --git a/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb
index a5ad2f952d32dc211938447a87111b7de60b1f0b..f3d489aad0d31525f5a42a3f928b2091efc78461 100644
--- a/lib/gitlab/i18n.rb
+++ b/lib/gitlab/i18n.rb
@@ -11,7 +11,9 @@ module Gitlab
       'zh_CN' => '简体中文',
       'zh_HK' => '繁體中文(香港)',
       'zh_TW' => '繁體中文(臺灣)',
-      'bg' => 'български'
+      'bg' => 'български',
+      'eo' => 'Esperanto',
+      'it' => 'Italiano'
     }.freeze
 
     def available_locales
diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb
index 27d5a9198b69d22a30257b42733798c920944010..3470a09eaf0338973b467bab406b25376b33e36f 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.7'.freeze
+    VERSION = '0.1.8'.freeze
     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 ff2b1d08c3c393957be114b184f5cf849862f404..1860352c96d1a5f2158ae84f8ab008ab77c7d68f 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -26,7 +26,8 @@ project_tree:
     - notes:
       - :author
       - :events
-    - :merge_request_diff
+    - merge_request_diff:
+      - :merge_request_diff_files
     - :events
     - :timelogs
     - label_links:
@@ -92,10 +93,13 @@ excluded_attributes:
     - :expired_at
   merge_request_diff:
     - :st_diffs
+  merge_request_diff_files:
+    - :diff
   issues:
     - :milestone_id
   merge_requests:
     - :milestone_id
+    - :ref_fetched
   award_emoji:
     - :awardable_id
   statuses:
@@ -113,6 +117,8 @@ methods:
     - :type
   merge_request_diff:
     - :utf8_st_diffs
+  merge_request_diff_files:
+    - :utf8_diff
   merge_requests:
     - :diff_head_sha
   project:
diff --git a/lib/gitlab/import_export/json_hash_builder.rb b/lib/gitlab/import_export/json_hash_builder.rb
index 48c09dafcb6b76dc02e3c3f6dd845827d53559f2..b48f63bcd7ec54870b524f2dacd2bb44a7c6e372 100644
--- a/lib/gitlab/import_export/json_hash_builder.rb
+++ b/lib/gitlab/import_export/json_hash_builder.rb
@@ -83,7 +83,9 @@ module Gitlab
       # +value+ existing model to be included in the hash
       # +json_config_hash+ the original hash containing the root model
       def add_model_value(current_key, value, json_config_hash)
-        @attributes_finder.parse(value) { |hash| value = { value => hash } }
+        @attributes_finder.parse(value) do |hash|
+          value = { value => hash } unless value.is_a?(Hash)
+        end
 
         add_to_array(current_key, json_config_hash, value)
       end
diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb
index 695852526cb66ee148948ce0f0e809036f886aa3..205804590460c75b1cb706af3ee2d8adb34623fc 100644
--- a/lib/gitlab/import_export/relation_factory.rb
+++ b/lib/gitlab/import_export/relation_factory.rb
@@ -71,6 +71,7 @@ module Gitlab
 
         @relation_hash['data'].deep_symbolize_keys! if @relation_name == :events && @relation_hash['data']
         set_st_diff_commits if @relation_name == :merge_request_diff
+        set_diff if @relation_name == :merge_request_diff_files
       end
 
       def update_user_references
@@ -202,6 +203,10 @@ module Gitlab
         HashUtil.deep_symbolize_array_with_date!(@relation_hash['st_commits'])
       end
 
+      def set_diff
+        @relation_hash['diff'] = @relation_hash.delete('utf8_diff')
+      end
+
       def existing_or_new_object
         # Only find existing records to avoid mapping tables such as milestones
         # Otherwise always create the record, skipping the extra SELECT clause.
diff --git a/lib/gitlab/job_waiter.rb b/lib/gitlab/job_waiter.rb
index 8db91d25a4bc586b97072eff1f411fb235cc087c..208f0e1bbeaeb9854dc87688732a7e825e09c322 100644
--- a/lib/gitlab/job_waiter.rb
+++ b/lib/gitlab/job_waiter.rb
@@ -14,7 +14,7 @@ module Gitlab
     # 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)
+    def wait(timeout = 10)
       start = Time.current
 
       while (Time.current - start) <= timeout
diff --git a/lib/gitlab/ldap/access.rb b/lib/gitlab/ldap/access.rb
index 54a5b1d31cd4d80c8e898619715c1e45634066bb..fb68627dedfee65d2c268992a46cd28aa6281c59 100644
--- a/lib/gitlab/ldap/access.rb
+++ b/lib/gitlab/ldap/access.rb
@@ -16,8 +16,8 @@ module Gitlab
       def self.allowed?(user)
         self.open(user) do |access|
           if access.allowed?
-            user.last_credential_check_at = Time.now
-            user.save
+            Users::UpdateService.new(user, last_credential_check_at: Time.now).execute
+
             true
           else
             false
diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb
index 5e299e26c54058ce94a7b48f8957edd67eac4691..39180dc17d992cd1f19434060a5908715726795a 100644
--- a/lib/gitlab/ldap/user.rb
+++ b/lib/gitlab/ldap/user.rb
@@ -10,9 +10,9 @@ module Gitlab
       class << self
         def find_by_uid_and_provider(uid, provider)
           # LDAP distinguished name is case-insensitive
-          identity = ::Identity.
-            where(provider: provider).
-            iwhere(extern_uid: uid).last
+          identity = ::Identity
+            .where(provider: provider)
+            .iwhere(extern_uid: uid).last
           identity && identity.user
         end
       end
diff --git a/lib/gitlab/metrics/base_sampler.rb b/lib/gitlab/metrics/base_sampler.rb
new file mode 100644
index 0000000000000000000000000000000000000000..219accfc029459510b429f79ba772e42559192f8
--- /dev/null
+++ b/lib/gitlab/metrics/base_sampler.rb
@@ -0,0 +1,94 @@
+require 'logger'
+module Gitlab
+  module Metrics
+    class BaseSampler
+      def self.initialize_instance(*args)
+        raise "#{name} singleton instance already initialized" if @instance
+        @instance = new(*args)
+        at_exit(&@instance.method(:stop))
+        @instance
+      end
+
+      def self.instance
+        @instance
+      end
+
+      attr_reader :running
+
+      # interval - The sampling interval in seconds.
+      def initialize(interval)
+        interval_half = interval.to_f / 2
+
+        @interval = interval
+        @interval_steps = (-interval_half..interval_half).step(0.1).to_a
+
+        @mutex = Mutex.new
+      end
+
+      def enabled?
+        true
+      end
+
+      def start
+        return unless enabled?
+
+        @mutex.synchronize do
+          return if running
+          @running = true
+
+          @thread = Thread.new do
+            sleep(sleep_interval)
+
+            while running
+              safe_sample
+
+              sleep(sleep_interval)
+            end
+          end
+        end
+      end
+
+      def stop
+        @mutex.synchronize do
+          return unless running
+
+          @running = false
+
+          if @thread
+            @thread.wakeup if @thread.alive?
+            @thread.join
+            @thread = nil
+          end
+        end
+      end
+
+      def safe_sample
+        sample
+      rescue => e
+        Rails.logger.warn("#{self.class}: #{e}, stopping")
+        stop
+      end
+
+      def sample
+        raise NotImplementedError
+      end
+
+      # Returns the sleep interval with a random adjustment.
+      #
+      # The random adjustment is put in place to ensure we:
+      #
+      # 1. Don't generate samples at the exact same interval every time (thus
+      #    potentially missing anything that happens in between samples).
+      # 2. Don't sample data at the same interval two times in a row.
+      def sleep_interval
+        while step = @interval_steps.sample
+          if step != @last_step
+            @last_step = step
+
+            return @interval + @last_step
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/metrics/connection_rack_middleware.rb b/lib/gitlab/metrics/connection_rack_middleware.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b3da360be8f3500aaa3fb28bf6fff5e98ba92baa
--- /dev/null
+++ b/lib/gitlab/metrics/connection_rack_middleware.rb
@@ -0,0 +1,45 @@
+module Gitlab
+  module Metrics
+    class ConnectionRackMiddleware
+      def initialize(app)
+        @app = app
+      end
+
+      def self.rack_request_count
+        @rack_request_count ||= Gitlab::Metrics.counter(:rack_request, 'Rack request count')
+      end
+
+      def self.rack_response_count
+        @rack_response_count ||= Gitlab::Metrics.counter(:rack_response, 'Rack response count')
+      end
+
+      def self.rack_uncaught_errors_count
+        @rack_uncaught_errors_count ||= Gitlab::Metrics.counter(:rack_uncaught_errors, 'Rack connections handling uncaught errors count')
+      end
+
+      def self.rack_execution_time
+        @rack_execution_time ||= Gitlab::Metrics.histogram(:rack_execution_time, 'Rack connection handling execution time',
+                                                           {}, [0.05, 0.1, 0.25, 0.5, 0.7, 1, 1.5, 2, 2.5, 3, 5, 7, 10])
+      end
+
+      def call(env)
+        method = env['REQUEST_METHOD'].downcase
+        started = Time.now.to_f
+        begin
+          ConnectionRackMiddleware.rack_request_count.increment(method: method)
+
+          status, headers, body = @app.call(env)
+
+          ConnectionRackMiddleware.rack_response_count.increment(method: method, status: status)
+          [status, headers, body]
+        rescue
+          ConnectionRackMiddleware.rack_uncaught_errors_count.increment
+          raise
+        ensure
+          elapsed = Time.now.to_f - started
+          ConnectionRackMiddleware.rack_execution_time.observe({}, elapsed)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/metrics/influx_db.rb b/lib/gitlab/metrics/influx_db.rb
index 3a39791edbfb1f37ba792b4a2d14885d4988845a..d7c56463aac4488c15a773659f83d35fe205f52b 100644
--- a/lib/gitlab/metrics/influx_db.rb
+++ b/lib/gitlab/metrics/influx_db.rb
@@ -157,8 +157,8 @@ module Gitlab
                 host = settings[:host]
                 port = settings[:port]
 
-                InfluxDB::Client.
-                  new(udp: { host: host, port: port })
+                InfluxDB::Client
+                  .new(udp: { host: host, port: port })
               end
             end
           end
diff --git a/lib/gitlab/metrics/sampler.rb b/lib/gitlab/metrics/influx_sampler.rb
similarity index 71%
rename from lib/gitlab/metrics/sampler.rb
rename to lib/gitlab/metrics/influx_sampler.rb
index 0000450d9bb6f7ba7d47ef6a54ca67834de13729..6db1dd755b7376e5d9b788c67e28d46d9858ea7e 100644
--- a/lib/gitlab/metrics/sampler.rb
+++ b/lib/gitlab/metrics/influx_sampler.rb
@@ -5,14 +5,11 @@ module Gitlab
     # This class is used to gather statistics that can't be directly associated
     # with a transaction such as system memory usage, garbage collection
     # statistics, etc.
-    class Sampler
+    class InfluxSampler < BaseSampler
       # interval - The sampling interval in seconds.
       def initialize(interval = Metrics.settings[:sample_interval])
-        interval_half = interval.to_f / 2
-
-        @interval       = interval
-        @interval_steps = (-interval_half..interval_half).step(0.1).to_a
-        @last_step      = nil
+        super(interval)
+        @last_step = nil
 
         @metrics = []
 
@@ -26,18 +23,6 @@ module Gitlab
         end
       end
 
-      def start
-        Thread.new do
-          Thread.current.abort_on_exception = true
-
-          loop do
-            sleep(sleep_interval)
-
-            sample
-          end
-        end
-      end
-
       def sample
         sample_memory_usage
         sample_file_descriptors
@@ -86,7 +71,7 @@ module Gitlab
       end
 
       def sample_gc
-        time  = GC::Profiler.total_time * 1000.0
+        time = GC::Profiler.total_time * 1000.0
         stats = GC.stat.merge(total_time: time)
 
         # We want the difference of GC runs compared to the last sample, not the
@@ -111,23 +96,6 @@ module Gitlab
       def sidekiq?
         Sidekiq.server?
       end
-
-      # Returns the sleep interval with a random adjustment.
-      #
-      # The random adjustment is put in place to ensure we:
-      #
-      # 1. Don't generate samples at the exact same interval every time (thus
-      #    potentially missing anything that happens in between samples).
-      # 2. Don't sample data at the same interval two times in a row.
-      def sleep_interval
-        while step = @interval_steps.sample
-          if step != @last_step
-            @last_step = step
-
-            return @interval + @last_step
-          end
-        end
-      end
     end
   end
 end
diff --git a/lib/gitlab/metrics/prometheus.rb b/lib/gitlab/metrics/prometheus.rb
index 606865093324fe7285fd4cbae746b220e7180eed..fb7bbc7cfc7f458d7c6af7aac64a96f853b8e469 100644
--- a/lib/gitlab/metrics/prometheus.rb
+++ b/lib/gitlab/metrics/prometheus.rb
@@ -5,8 +5,16 @@ module Gitlab
     module Prometheus
       include Gitlab::CurrentSettings
 
+      def metrics_folder_present?
+        ENV.has_key?('prometheus_multiproc_dir') &&
+          ::Dir.exist?(ENV['prometheus_multiproc_dir']) &&
+          ::File.writable?(ENV['prometheus_multiproc_dir'])
+      end
+
       def prometheus_metrics_enabled?
-        @prometheus_metrics_enabled ||= current_application_settings[:prometheus_metrics_enabled] || false
+        return @prometheus_metrics_enabled if defined?(@prometheus_metrics_enabled)
+
+        @prometheus_metrics_enabled = prometheus_metrics_enabled_unmemoized
       end
 
       def registry
@@ -21,8 +29,8 @@ module Gitlab
         provide_metric(name) || registry.summary(name, docstring, base_labels)
       end
 
-      def gauge(name, docstring, base_labels = {})
-        provide_metric(name) || registry.gauge(name, docstring, base_labels)
+      def gauge(name, docstring, base_labels = {}, multiprocess_mode = :all)
+        provide_metric(name) || registry.gauge(name, docstring, base_labels, multiprocess_mode)
       end
 
       def histogram(name, docstring, base_labels = {}, buckets = ::Prometheus::Client::Histogram::DEFAULT_BUCKETS)
@@ -36,6 +44,12 @@ module Gitlab
           NullMetric.new
         end
       end
+
+      private
+
+      def prometheus_metrics_enabled_unmemoized
+        metrics_folder_present? && current_application_settings[:prometheus_metrics_enabled] || false
+      end
     end
   end
 end
diff --git a/lib/gitlab/metrics/system.rb b/lib/gitlab/metrics/system.rb
index 3aaebb3e9c3e64eda8c52eedc54f12292db64ae5..aba3e0df382e7ad58dfc6332fd77c40ae6b40c79 100644
--- a/lib/gitlab/metrics/system.rb
+++ b/lib/gitlab/metrics/system.rb
@@ -34,13 +34,13 @@ module Gitlab
       # THREAD_CPUTIME is not supported on OS X
       if Process.const_defined?(:CLOCK_THREAD_CPUTIME_ID)
         def self.cpu_time
-          Process.
-            clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :millisecond)
+          Process
+            .clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :millisecond)
         end
       else
         def self.cpu_time
-          Process.
-            clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID, :millisecond)
+          Process
+            .clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID, :millisecond)
         end
       end
 
diff --git a/lib/gitlab/metrics/unicorn_sampler.rb b/lib/gitlab/metrics/unicorn_sampler.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f69872520398a1ad8b160b271d816642231f1217
--- /dev/null
+++ b/lib/gitlab/metrics/unicorn_sampler.rb
@@ -0,0 +1,48 @@
+module Gitlab
+  module Metrics
+    class UnicornSampler < BaseSampler
+      def initialize(interval)
+        super(interval)
+      end
+
+      def unicorn_active_connections
+        @unicorn_active_connections ||= Gitlab::Metrics.gauge(:unicorn_active_connections, 'Unicorn active connections', {}, :max)
+      end
+
+      def unicorn_queued_connections
+        @unicorn_queued_connections ||= Gitlab::Metrics.gauge(:unicorn_queued_connections, 'Unicorn queued connections', {}, :max)
+      end
+
+      def enabled?
+        # Raindrops::Linux.tcp_listener_stats is only present on Linux
+        unicorn_with_listeners? && Raindrops::Linux.respond_to?(:tcp_listener_stats)
+      end
+
+      def sample
+        Raindrops::Linux.tcp_listener_stats(tcp_listeners).each do |addr, stats|
+          unicorn_active_connections.set({ type: 'tcp', address: addr }, stats.active)
+          unicorn_queued_connections.set({ type: 'tcp', address: addr }, stats.queued)
+        end
+
+        Raindrops::Linux.unix_listener_stats(unix_listeners).each do |addr, stats|
+          unicorn_active_connections.set({ type: 'unix', address: addr }, stats.active)
+          unicorn_queued_connections.set({ type: 'unix', address: addr }, stats.queued)
+        end
+      end
+
+      private
+
+      def tcp_listeners
+        @tcp_listeners ||= Unicorn.listener_names.grep(%r{\A[^/]+:\d+\z})
+      end
+
+      def unix_listeners
+        @unix_listeners ||= Unicorn.listener_names - tcp_listeners
+      end
+
+      def unicorn_with_listeners?
+        defined?(Unicorn) && Unicorn.listener_names.any?
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb
index 7307f8c2c871156a49d30832b9e7c87c6a43b9c1..b3f453e506d191b212832958a500030bb806dc40 100644
--- a/lib/gitlab/o_auth/user.rb
+++ b/lib/gitlab/o_auth/user.rb
@@ -32,7 +32,7 @@ module Gitlab
 
         block_after_save = needs_blocking?
 
-        gl_user.save!
+        Users::UpdateService.new(gl_user).execute!
 
         gl_user.block if block_after_save
 
diff --git a/lib/gitlab/other_markup.rb b/lib/gitlab/other_markup.rb
index 31a24460f0f7e591a260b56ca846884db04b5e5d..fc3f21233dd82442b5577f232aef72fc0a8c0830 100644
--- a/lib/gitlab/other_markup.rb
+++ b/lib/gitlab/other_markup.rb
@@ -6,8 +6,8 @@ module Gitlab
     # input         - the source text in a markup format
     #
     def self.render(file_name, input, context)
-      html = GitHub::Markup.render(file_name, input).
-        force_encoding(input.encoding)
+      html = GitHub::Markup.render(file_name, input)
+        .force_encoding(input.encoding)
       context[:pipeline] = :markup
 
       html = Banzai.render(html, context)
diff --git a/lib/gitlab/performance_bar/peek_query_tracker.rb b/lib/gitlab/performance_bar/peek_query_tracker.rb
index 7ab80f5ee0fc46ee24195c19e5f5f18fc3746d30..574ae8731a543c006509a3f81c7ce276e554eda1 100644
--- a/lib/gitlab/performance_bar/peek_query_tracker.rb
+++ b/lib/gitlab/performance_bar/peek_query_tracker.rb
@@ -3,8 +3,8 @@ module Gitlab
   module PerformanceBar
     module PeekQueryTracker
       def sorted_queries
-        PEEK_DB_CLIENT.query_details.
-          sort { |a, b| b[:duration] <=> a[:duration] }
+        PEEK_DB_CLIENT.query_details
+          .sort { |a, b| b[:duration] <=> a[:duration] }
       end
 
       def results
diff --git a/lib/gitlab/project_authorizations/with_nested_groups.rb b/lib/gitlab/project_authorizations/with_nested_groups.rb
index bb0df1e3dad25af485134213816026fe1e77739c..15b8beacf605574b581e434301ffa35c64fa2f02 100644
--- a/lib/gitlab/project_authorizations/with_nested_groups.rb
+++ b/lib/gitlab/project_authorizations/with_nested_groups.rb
@@ -28,34 +28,34 @@ module Gitlab
 
           # Projects that belong directly to any of the groups the user has
           # access to.
-          Namespace.
-            unscoped.
-            select([alias_as_column(projects[:id], 'project_id'),
-                    cte_alias[:access_level]]).
-            from(cte_alias).
-            joins(:projects),
+          Namespace
+            .unscoped
+            .select([alias_as_column(projects[:id], 'project_id'),
+                     cte_alias[:access_level]])
+            .from(cte_alias)
+            .joins(:projects),
 
           # Projects shared with any of the namespaces the user has access to.
-          Namespace.
-            unscoped.
-            select([links[:project_id],
-                    least(cte_alias[:access_level],
-                          links[:group_access],
-                          'access_level')]).
-            from(cte_alias).
-            joins('INNER JOIN project_group_links ON project_group_links.group_id = namespaces.id').
-            joins('INNER JOIN projects ON projects.id = project_group_links.project_id').
-            joins('INNER JOIN namespaces p_ns ON p_ns.id = projects.namespace_id').
-            where('p_ns.share_with_group_lock IS FALSE')
+          Namespace
+            .unscoped
+            .select([links[:project_id],
+                     least(cte_alias[:access_level],
+                           links[:group_access],
+                           'access_level')])
+            .from(cte_alias)
+            .joins('INNER JOIN project_group_links ON project_group_links.group_id = namespaces.id')
+            .joins('INNER JOIN projects ON projects.id = project_group_links.project_id')
+            .joins('INNER JOIN namespaces p_ns ON p_ns.id = projects.namespace_id')
+            .where('p_ns.share_with_group_lock IS FALSE')
         ]
 
         union = Gitlab::SQL::Union.new(relations)
 
-        ProjectAuthorization.
-          unscoped.
-          with.
-          recursive(cte.to_arel).
-          select_from_union(union)
+        ProjectAuthorization
+          .unscoped
+          .with
+          .recursive(cte.to_arel)
+          .select_from_union(union)
       end
 
       private
@@ -68,17 +68,17 @@ module Gitlab
         namespaces = Namespace.arel_table
 
         # Namespaces the user is a member of.
-        cte << user.groups.
-          select([namespaces[:id], members[:access_level]]).
-          except(:order)
+        cte << user.groups
+          .select([namespaces[:id], members[:access_level]])
+          .except(:order)
 
         # Sub groups of any groups the user is a member of.
         cte << Group.select([namespaces[:id],
                              greatest(members[:access_level],
-                                      cte.table[:access_level], 'access_level')]).
-          joins(join_cte(cte)).
-          joins(join_members).
-          except(:order)
+                                      cte.table[:access_level], 'access_level')])
+          .joins(join_cte(cte))
+          .joins(join_members)
+          .except(:order)
 
         cte
       end
@@ -88,11 +88,11 @@ module Gitlab
         members = Member.arel_table
         namespaces = Namespace.arel_table
 
-        cond = members[:source_id].
-          eq(namespaces[:id]).
-          and(members[:source_type].eq('Namespace')).
-          and(members[:requested_at].eq(nil)).
-          and(members[:user_id].eq(user.id))
+        cond = members[:source_id]
+          .eq(namespaces[:id])
+          .and(members[:source_type].eq('Namespace'))
+          .and(members[:requested_at].eq(nil))
+          .and(members[:user_id].eq(user.id))
 
         Arel::Nodes::OuterJoin.new(members, Arel::Nodes::On.new(cond))
       end
diff --git a/lib/gitlab/project_authorizations/without_nested_groups.rb b/lib/gitlab/project_authorizations/without_nested_groups.rb
index 627e8c5fba26f8129418ced650a803972bf7e606..ad87540e6c2a3d53c0dc82ce12ffc6a94774e819 100644
--- a/lib/gitlab/project_authorizations/without_nested_groups.rb
+++ b/lib/gitlab/project_authorizations/without_nested_groups.rb
@@ -26,9 +26,9 @@ module Gitlab
 
         union = Gitlab::SQL::Union.new(relations)
 
-        ProjectAuthorization.
-          unscoped.
-          select_from_union(union)
+        ProjectAuthorization
+          .unscoped
+          .select_from_union(union)
       end
     end
   end
diff --git a/lib/gitlab/prometheus/additional_metrics_parser.rb b/lib/gitlab/prometheus/additional_metrics_parser.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cb95daf22602b68b3aea27aeab7aabe0508cee06
--- /dev/null
+++ b/lib/gitlab/prometheus/additional_metrics_parser.rb
@@ -0,0 +1,34 @@
+module Gitlab
+  module Prometheus
+    module AdditionalMetricsParser
+      extend self
+
+      def load_groups_from_yaml
+        additional_metrics_raw.map(&method(:group_from_entry))
+      end
+
+      private
+
+      def validate!(obj)
+        raise ParsingError.new(obj.errors.full_messages.join('\n')) unless obj.valid?
+      end
+
+      def group_from_entry(entry)
+        entry[:name] = entry.delete(:group)
+        entry[:metrics]&.map! do |entry|
+          Metric.new(entry).tap(&method(:validate!))
+        end
+
+        MetricGroup.new(entry).tap(&method(:validate!))
+      end
+
+      def additional_metrics_raw
+        load_yaml_file&.map(&:deep_symbolize_keys).freeze
+      end
+
+      def load_yaml_file
+        @loaded_yaml_file ||= YAML.load_file(Rails.root.join('config/prometheus/additional_metrics.yml'))
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/prometheus/metric.rb b/lib/gitlab/prometheus/metric.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f54b2c6aaffa9a1647fa249c1e5bfefa94022a14
--- /dev/null
+++ b/lib/gitlab/prometheus/metric.rb
@@ -0,0 +1,16 @@
+module Gitlab
+  module Prometheus
+    class Metric
+      include ActiveModel::Model
+
+      attr_accessor :title, :required_metrics, :weight, :y_label, :queries
+
+      validates :title, :required_metrics, :weight, :y_label, :queries, presence: true
+
+      def initialize(params = {})
+        super(params)
+        @y_label ||= 'Values'
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/prometheus/metric_group.rb b/lib/gitlab/prometheus/metric_group.rb
new file mode 100644
index 0000000000000000000000000000000000000000..729fef34b35e83c8566b25b8aee838216bf28d61
--- /dev/null
+++ b/lib/gitlab/prometheus/metric_group.rb
@@ -0,0 +1,14 @@
+module Gitlab
+  module Prometheus
+    class MetricGroup
+      include ActiveModel::Model
+
+      attr_accessor :name, :priority, :metrics
+      validates :name, :priority, :metrics, presence: true
+
+      def self.all
+        AdditionalMetricsParser.load_groups_from_yaml
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/prometheus/parsing_error.rb b/lib/gitlab/prometheus/parsing_error.rb
new file mode 100644
index 0000000000000000000000000000000000000000..49cc0e160802ac429fb3cb712db38aba53036acf
--- /dev/null
+++ b/lib/gitlab/prometheus/parsing_error.rb
@@ -0,0 +1,5 @@
+module Gitlab
+  module Prometheus
+    ParsingError = Class.new(StandardError)
+  end
+end
diff --git a/lib/gitlab/prometheus/queries/additional_metrics_deployment_query.rb b/lib/gitlab/prometheus/queries/additional_metrics_deployment_query.rb
new file mode 100644
index 0000000000000000000000000000000000000000..67c69d9ccf3356a9f889bd81ca32987cf5016ab1
--- /dev/null
+++ b/lib/gitlab/prometheus/queries/additional_metrics_deployment_query.rb
@@ -0,0 +1,22 @@
+module Gitlab
+  module Prometheus
+    module Queries
+      class AdditionalMetricsDeploymentQuery < BaseQuery
+        include QueryAdditionalMetrics
+
+        def query(deployment_id)
+          Deployment.find_by(id: deployment_id).try do |deployment|
+            query_context = {
+              environment_slug: deployment.environment.slug,
+              environment_filter: %{container_name!="POD",environment="#{deployment.environment.slug}"},
+              timeframe_start: (deployment.created_at - 30.minutes).to_f,
+              timeframe_end: (deployment.created_at + 30.minutes).to_f
+            }
+
+            query_metrics(query_context)
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/prometheus/queries/additional_metrics_environment_query.rb b/lib/gitlab/prometheus/queries/additional_metrics_environment_query.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b5a679ddd79e4aa1c017618dfcc9693465f91402
--- /dev/null
+++ b/lib/gitlab/prometheus/queries/additional_metrics_environment_query.rb
@@ -0,0 +1,22 @@
+module Gitlab
+  module Prometheus
+    module Queries
+      class AdditionalMetricsEnvironmentQuery < BaseQuery
+        include QueryAdditionalMetrics
+
+        def query(environment_id)
+          Environment.find_by(id: environment_id).try do |environment|
+            query_context = {
+              environment_slug: environment.slug,
+              environment_filter: %{container_name!="POD",environment="#{environment.slug}"},
+              timeframe_start: 8.hours.ago.to_f,
+              timeframe_end: Time.now.to_f
+            }
+
+            query_metrics(query_context)
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/prometheus/queries/base_query.rb b/lib/gitlab/prometheus/queries/base_query.rb
index 2a2eb4ae57fbcfa1fe5fe6a6c4bb684979a4a9ba..c60828165bd9bcd09d3319530a006fb31ed3ad7b 100644
--- a/lib/gitlab/prometheus/queries/base_query.rb
+++ b/lib/gitlab/prometheus/queries/base_query.rb
@@ -3,7 +3,7 @@ module Gitlab
     module Queries
       class BaseQuery
         attr_accessor :client
-        delegate :query_range, :query, to: :client, prefix: true
+        delegate :query_range, :query, :label_values, :series, to: :client, prefix: true
 
         def raw_memory_usage_query(environment_slug)
           %{avg(container_memory_usage_bytes{container_name!="POD",environment="#{environment_slug}"}) / 2^20}
diff --git a/lib/gitlab/prometheus/queries/deployment_query.rb b/lib/gitlab/prometheus/queries/deployment_query.rb
index 2cc08731f8d459cbb5be5638b90daba933d60016..170f483540eddef36c05596112beb9547caaa275 100644
--- a/lib/gitlab/prometheus/queries/deployment_query.rb
+++ b/lib/gitlab/prometheus/queries/deployment_query.rb
@@ -1,26 +1,31 @@
-module Gitlab::Prometheus::Queries
-  class DeploymentQuery < BaseQuery
-    def query(deployment_id)
-      deployment = Deployment.find_by(id: deployment_id)
-      environment_slug = deployment.environment.slug
+module Gitlab
+  module Prometheus
+    module Queries
+      class DeploymentQuery < BaseQuery
+        def query(deployment_id)
+          Deployment.find_by(id: deployment_id).try do |deployment|
+            environment_slug = deployment.environment.slug
 
-      memory_query = raw_memory_usage_query(environment_slug)
-      memory_avg_query = %{avg(avg_over_time(container_memory_usage_bytes{container_name!="POD",environment="#{environment_slug}"}[30m]))}
-      cpu_query = raw_cpu_usage_query(environment_slug)
-      cpu_avg_query = %{avg(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="#{environment_slug}"}[30m])) * 100}
+            memory_query = raw_memory_usage_query(environment_slug)
+            memory_avg_query = %{avg(avg_over_time(container_memory_usage_bytes{container_name!="POD",environment="#{environment_slug}"}[30m]))}
+            cpu_query = raw_cpu_usage_query(environment_slug)
+            cpu_avg_query = %{avg(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="#{environment_slug}"}[30m])) * 100}
 
-      timeframe_start = (deployment.created_at - 30.minutes).to_f
-      timeframe_end = (deployment.created_at + 30.minutes).to_f
+            timeframe_start = (deployment.created_at - 30.minutes).to_f
+            timeframe_end = (deployment.created_at + 30.minutes).to_f
 
-      {
-        memory_values: client_query_range(memory_query, start: timeframe_start, stop: timeframe_end),
-        memory_before: client_query(memory_avg_query, time: deployment.created_at.to_f),
-        memory_after: client_query(memory_avg_query, time: timeframe_end),
+            {
+              memory_values: client_query_range(memory_query, start: timeframe_start, stop: timeframe_end),
+              memory_before: client_query(memory_avg_query, time: deployment.created_at.to_f),
+              memory_after: client_query(memory_avg_query, time: timeframe_end),
 
-        cpu_values: client_query_range(cpu_query, start: timeframe_start, stop: timeframe_end),
-        cpu_before: client_query(cpu_avg_query, time: deployment.created_at.to_f),
-        cpu_after: client_query(cpu_avg_query, time: timeframe_end)
-      }
+              cpu_values: client_query_range(cpu_query, start: timeframe_start, stop: timeframe_end),
+              cpu_before: client_query(cpu_avg_query, time: deployment.created_at.to_f),
+              cpu_after: client_query(cpu_avg_query, time: timeframe_end)
+            }
+          end
+        end
+      end
     end
   end
 end
diff --git a/lib/gitlab/prometheus/queries/environment_query.rb b/lib/gitlab/prometheus/queries/environment_query.rb
index 01d756d7284e8cfe53cca429b79ce5d9e8c5cdcd..66f29d95177b683f1c32d971fdf4fca9a96e137d 100644
--- a/lib/gitlab/prometheus/queries/environment_query.rb
+++ b/lib/gitlab/prometheus/queries/environment_query.rb
@@ -1,20 +1,25 @@
-module Gitlab::Prometheus::Queries
-  class EnvironmentQuery < BaseQuery
-    def query(environment_id)
-      environment = Environment.find_by(id: environment_id)
-      environment_slug = environment.slug
-      timeframe_start = 8.hours.ago.to_f
-      timeframe_end = Time.now.to_f
+module Gitlab
+  module Prometheus
+    module Queries
+      class EnvironmentQuery < BaseQuery
+        def query(environment_id)
+          Environment.find_by(id: environment_id).try do |environment|
+            environment_slug = environment.slug
+            timeframe_start = 8.hours.ago.to_f
+            timeframe_end = Time.now.to_f
 
-      memory_query = raw_memory_usage_query(environment_slug)
-      cpu_query = raw_cpu_usage_query(environment_slug)
+            memory_query = raw_memory_usage_query(environment_slug)
+            cpu_query = raw_cpu_usage_query(environment_slug)
 
-      {
-        memory_values: client_query_range(memory_query, start: timeframe_start, stop: timeframe_end),
-        memory_current: client_query(memory_query, time: timeframe_end),
-        cpu_values: client_query_range(cpu_query, start: timeframe_start, stop: timeframe_end),
-        cpu_current: client_query(cpu_query, time: timeframe_end)
-      }
+            {
+              memory_values: client_query_range(memory_query, start: timeframe_start, stop: timeframe_end),
+              memory_current: client_query(memory_query, time: timeframe_end),
+              cpu_values: client_query_range(cpu_query, start: timeframe_start, stop: timeframe_end),
+              cpu_current: client_query(cpu_query, time: timeframe_end)
+            }
+          end
+        end
+      end
     end
   end
 end
diff --git a/lib/gitlab/prometheus/queries/matched_metrics_query.rb b/lib/gitlab/prometheus/queries/matched_metrics_query.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d4894c87f8d6149e02ea9569820bdc6c68bd716a
--- /dev/null
+++ b/lib/gitlab/prometheus/queries/matched_metrics_query.rb
@@ -0,0 +1,80 @@
+module Gitlab
+  module Prometheus
+    module Queries
+      class MatchedMetricsQuery < BaseQuery
+        MAX_QUERY_ITEMS = 40.freeze
+
+        def query
+          groups_data.map do |group, data|
+            {
+              group: group.name,
+              priority: group.priority,
+              active_metrics: data[:active_metrics],
+              metrics_missing_requirements: data[:metrics_missing_requirements]
+            }
+          end
+        end
+
+        private
+
+        def groups_data
+          metrics_groups = groups_with_active_metrics(Gitlab::Prometheus::MetricGroup.all)
+          lookup = active_series_lookup(metrics_groups)
+
+          groups = {}
+
+          metrics_groups.each do |group|
+            groups[group] ||= { active_metrics: 0, metrics_missing_requirements: 0 }
+            active_metrics = group.metrics.count { |metric| metric.required_metrics.all?(&lookup.method(:has_key?)) }
+
+            groups[group][:active_metrics] += active_metrics
+            groups[group][:metrics_missing_requirements] += group.metrics.count - active_metrics
+          end
+
+          groups
+        end
+
+        def active_series_lookup(metric_groups)
+          timeframe_start = 8.hours.ago
+          timeframe_end = Time.now
+
+          series = metric_groups.flat_map(&:metrics).flat_map(&:required_metrics).uniq
+
+          lookup = series.each_slice(MAX_QUERY_ITEMS).flat_map do |batched_series|
+            client_series(*batched_series, start: timeframe_start, stop: timeframe_end)
+              .select(&method(:has_matching_label))
+              .map { |series_info| [series_info['__name__'], true] }
+          end
+          lookup.to_h
+        end
+
+        def has_matching_label(series_info)
+          series_info.key?('environment')
+        end
+
+        def available_metrics
+          @available_metrics ||= client_label_values || []
+        end
+
+        def filter_active_metrics(metric_group)
+          metric_group.metrics.select! do |metric|
+            metric.required_metrics.all?(&available_metrics.method(:include?))
+          end
+          metric_group
+        end
+
+        def groups_with_active_metrics(metric_groups)
+          metric_groups.map(&method(:filter_active_metrics)).select { |group| group.metrics.any? }
+        end
+
+        def metrics_with_required_series(metric_groups)
+          metric_groups.flat_map do |group|
+            group.metrics.select do |metric|
+              metric.required_metrics.all?(&available_metrics.method(:include?))
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/prometheus/queries/query_additional_metrics.rb b/lib/gitlab/prometheus/queries/query_additional_metrics.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e44be770544787d6f5971bfbc9d03b69704d596a
--- /dev/null
+++ b/lib/gitlab/prometheus/queries/query_additional_metrics.rb
@@ -0,0 +1,73 @@
+module Gitlab
+  module Prometheus
+    module Queries
+      module QueryAdditionalMetrics
+        def query_metrics(query_context)
+          query_processor = method(:process_query).curry[query_context]
+
+          groups = matched_metrics.map do |group|
+            metrics = group.metrics.map do |metric|
+              {
+                title: metric.title,
+                weight: metric.weight,
+                y_label: metric.y_label,
+                queries: metric.queries.map(&query_processor).select(&method(:query_with_result))
+              }
+            end
+
+            {
+              group: group.name,
+              priority: group.priority,
+              metrics: metrics.select(&method(:metric_with_any_queries))
+            }
+          end
+
+          groups.select(&method(:group_with_any_metrics))
+        end
+
+        private
+
+        def metric_with_any_queries(metric)
+          metric[:queries]&.count&.> 0
+        end
+
+        def group_with_any_metrics(group)
+          group[:metrics]&.count&.> 0
+        end
+
+        def query_with_result(query)
+          query[:result]&.any? do |item|
+            item&.[](:values)&.any? || item&.[](:value)&.any?
+          end
+        end
+
+        def process_query(context, query)
+          query_with_result = query.dup
+          result =
+            if query.key?(:query_range)
+              client_query_range(query[:query_range] % context, start: context[:timeframe_start], stop: context[:timeframe_end])
+            else
+              client_query(query[:query] % context, time: context[:timeframe_end])
+            end
+          query_with_result[:result] = result&.map(&:deep_symbolize_keys)
+          query_with_result
+        end
+
+        def available_metrics
+          @available_metrics ||= client_label_values || []
+        end
+
+        def matched_metrics
+          result = Gitlab::Prometheus::MetricGroup.all.map do |group|
+            group.metrics.select! do |metric|
+              metric.required_metrics.all?(&available_metrics.method(:include?))
+            end
+            group
+          end
+
+          result.select { |group| group.metrics.any? }
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/prometheus_client.rb b/lib/gitlab/prometheus_client.rb
index 5b51a1779dd27f3d81670206859ed78083e5fb33..aa94614bf180cf02546bed6c158d2868ac702ee2 100644
--- a/lib/gitlab/prometheus_client.rb
+++ b/lib/gitlab/prometheus_client.rb
@@ -29,6 +29,14 @@ module Gitlab
       end
     end
 
+    def label_values(name = '__name__')
+      json_api_get("label/#{name}/values")
+    end
+
+    def series(*matches, start: 8.hours.ago, stop: Time.now)
+      json_api_get('series', 'match': matches, start: start.to_f, end: stop.to_f)
+    end
+
     private
 
     def json_api_get(type, args = {})
diff --git a/lib/gitlab/slash_commands/command_definition.rb b/lib/gitlab/quick_actions/command_definition.rb
similarity index 98%
rename from lib/gitlab/slash_commands/command_definition.rb
rename to lib/gitlab/quick_actions/command_definition.rb
index caab88560145fd8b71c1a978bf921cca4236773b..3937d9c153a2a536f78fcbb9a53f378c9d2af0ee 100644
--- a/lib/gitlab/slash_commands/command_definition.rb
+++ b/lib/gitlab/quick_actions/command_definition.rb
@@ -1,5 +1,5 @@
 module Gitlab
-  module SlashCommands
+  module QuickActions
     class CommandDefinition
       attr_accessor :name, :aliases, :description, :explanation, :params,
         :condition_block, :parse_params_block, :action_block
diff --git a/lib/gitlab/slash_commands/dsl.rb b/lib/gitlab/quick_actions/dsl.rb
similarity index 96%
rename from lib/gitlab/slash_commands/dsl.rb
rename to lib/gitlab/quick_actions/dsl.rb
index 1b5b4566d8123c864b448e4dd38e5fd4f9301620..a4a97236ffc147897cf907d20a34b75c0911f207 100644
--- a/lib/gitlab/slash_commands/dsl.rb
+++ b/lib/gitlab/quick_actions/dsl.rb
@@ -1,5 +1,5 @@
 module Gitlab
-  module SlashCommands
+  module QuickActions
     module Dsl
       extend ActiveSupport::Concern
 
@@ -14,7 +14,7 @@ module Gitlab
       end
 
       class_methods do
-        # Allows to give a description to the next slash command.
+        # Allows to give a description to the next quick action.
         # This description is shown in the autocomplete menu.
         # It accepts a block that will be evaluated with the context given to
         # `CommandDefintion#to_h`.
@@ -31,7 +31,7 @@ module Gitlab
           @description = block_given? ? block : text
         end
 
-        # Allows to define params for the next slash command.
+        # Allows to define params for the next quick action.
         # These params are shown in the autocomplete menu.
         #
         # Example:
diff --git a/lib/gitlab/slash_commands/extractor.rb b/lib/gitlab/quick_actions/extractor.rb
similarity index 94%
rename from lib/gitlab/slash_commands/extractor.rb
rename to lib/gitlab/quick_actions/extractor.rb
index 6dbb467d70de6f819c69489797bd9774909bebc5..09576be715688f17fd87b28cec4f3ef907ef567a 100644
--- a/lib/gitlab/slash_commands/extractor.rb
+++ b/lib/gitlab/quick_actions/extractor.rb
@@ -1,10 +1,10 @@
 module Gitlab
-  module SlashCommands
+  module QuickActions
     # This class takes an array of commands that should be extracted from a
     # given text.
     #
     # ```
-    # extractor = Gitlab::SlashCommands::Extractor.new([:open, :assign, :labels])
+    # extractor = Gitlab::QuickActions::Extractor.new([:open, :assign, :labels])
     # ```
     class Extractor
       attr_reader :command_definitions
@@ -24,7 +24,7 @@ module Gitlab
       #
       # Usage:
       # ```
-      # extractor = Gitlab::SlashCommands::Extractor.new([:open, :assign, :labels])
+      # extractor = Gitlab::QuickActions::Extractor.new([:open, :assign, :labels])
       # msg = %(hello\n/labels ~foo ~"bar baz"\nworld)
       # commands = extractor.extract_commands(msg) #=> [['labels', '~foo ~"bar baz"']]
       # msg #=> "hello\nworld"
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index e4d2a9924707c748c8c373fe8dbb0df6558bc336..057f32eaef7a1e9d49ed8588b63c99d5dab8a5bd 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -19,14 +19,6 @@ module Gitlab
       "It must start with letter, digit, emoji or '_'."
     end
 
-    def file_name_regex
-      @file_name_regex ||= /\A[[[:alnum:]]_\-\.\@\+]*\z/.freeze
-    end
-
-    def file_name_regex_message
-      "can contain only letters, digits, '_', '-', '@', '+' and '.'."
-    end
-
     def container_registry_reference_regex
       Gitlab::PathRegex.git_reference_regex
     end
@@ -43,7 +35,7 @@ module Gitlab
     end
 
     def environment_name_regex_message
-      "can contain only letters, digits, '-', '_', '/', '$', '{', '}', '.' and spaces"
+      "can contain only letters, digits, '-', '_', '/', '$', '{', '}', '.', and spaces"
     end
 
     def kubernetes_namespace_regex
diff --git a/lib/gitlab/repo_path.rb b/lib/gitlab/repo_path.rb
index 878e03f61d70d41653a7772aa729cc62f7d76043..3591fa9145ef7cdb20ba774b042a5552685438c3 100644
--- a/lib/gitlab/repo_path.rb
+++ b/lib/gitlab/repo_path.rb
@@ -3,16 +3,18 @@ module Gitlab
     NotFoundError = Class.new(StandardError)
 
     def self.parse(repo_path)
+      wiki = false
       project_path = strip_storage_path(repo_path.sub(/\.git\z/, ''), fail_on_not_found: false)
-      project = Project.find_by_full_path(project_path)
-      if project_path.end_with?('.wiki') && !project
-        project = Project.find_by_full_path(project_path.chomp('.wiki'))
+      project, was_redirected = find_project(project_path)
+
+      if project_path.end_with?('.wiki') && project.nil?
+        project, was_redirected = find_project(project_path.chomp('.wiki'))
         wiki = true
-      else
-        wiki = false
       end
 
-      [project, wiki]
+      redirected_path = project_path if was_redirected
+
+      [project, wiki, redirected_path]
     end
 
     def self.strip_storage_path(repo_path, fail_on_not_found: true)
@@ -30,5 +32,12 @@ module Gitlab
 
       result.sub(/\A\/*/, '')
     end
+
+    def self.find_project(project_path)
+      project = Project.find_by_full_path(project_path, follow_redirects: true)
+      was_redirected = project && project.full_path.casecmp(project_path) != 0
+
+      [project, was_redirected]
+    end
   end
 end
diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb
index b1d6ea665b70c854911499c1b05854eab69366e6..0baea092e6aa674789b3503f17fc979d7a64229c 100644
--- a/lib/gitlab/shell.rb
+++ b/lib/gitlab/shell.rb
@@ -2,6 +2,8 @@ require 'securerandom'
 
 module Gitlab
   class Shell
+    GITLAB_SHELL_ENV_VARS = %w(GIT_TERMINAL_PROMPT).freeze
+
     Error = Class.new(StandardError)
 
     KeyAdder = Struct.new(:io) do
@@ -30,8 +32,8 @@ module Gitlab
       end
 
       def version_required
-        @version_required ||= File.read(Rails.root.
-                                        join('GITLAB_SHELL_VERSION')).strip
+        @version_required ||= File.read(Rails.root
+                                        .join('GITLAB_SHELL_VERSION')).strip
       end
 
       def strip_key(key)
@@ -67,8 +69,8 @@ module Gitlab
     #   add_repository("/path/to/storage", "gitlab/gitlab-ci")
     #
     def add_repository(storage, name)
-      Gitlab::Utils.system_silent([gitlab_shell_projects_path,
-                                   'add-project', storage, "#{name}.git"])
+      gitlab_shell_fast_execute([gitlab_shell_projects_path,
+                                 'add-project', storage, "#{name}.git"])
     end
 
     # Import repository
@@ -82,10 +84,9 @@ module Gitlab
     def import_repository(storage, name, url)
       # Timeout should be less than 900 ideally, to prevent the memory killer
       # to silently kill the process without knowing we are timing out here.
-      output, status = Popen.popen([gitlab_shell_projects_path, 'import-project',
-                                    storage, "#{name}.git", url, "#{Gitlab.config.gitlab_shell.git_timeout}"])
-      raise Error, output unless status.zero?
-      true
+      cmd = [gitlab_shell_projects_path, 'import-project',
+             storage, "#{name}.git", url, "#{Gitlab.config.gitlab_shell.git_timeout}"]
+      gitlab_shell_fast_execute_raise_error(cmd)
     end
 
     # Fetch remote for repository
@@ -103,9 +104,7 @@ module Gitlab
       args << '--force' if forced
       args << '--no-tags' if no_tags
 
-      output, status = Popen.popen(args)
-      raise Error, output unless status.zero?
-      true
+      gitlab_shell_fast_execute_raise_error(args)
     end
 
     # Move repository
@@ -117,8 +116,8 @@ module Gitlab
     #   mv_repository("/path/to/storage", "gitlab/gitlab-ci", "randx/gitlab-ci-new")
     #
     def mv_repository(storage, path, new_path)
-      Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'mv-project',
-                                   storage, "#{path}.git", "#{new_path}.git"])
+      gitlab_shell_fast_execute([gitlab_shell_projects_path, 'mv-project',
+                                 storage, "#{path}.git", "#{new_path}.git"])
     end
 
     # Fork repository to new namespace
@@ -131,9 +130,9 @@ module Gitlab
     #  fork_repository("/path/to/forked_from/storage", "gitlab/gitlab-ci", "/path/to/forked_to/storage", "randx")
     #
     def fork_repository(forked_from_storage, path, forked_to_storage, fork_namespace)
-      Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'fork-project',
-                                   forked_from_storage, "#{path}.git", forked_to_storage,
-                                   fork_namespace])
+      gitlab_shell_fast_execute([gitlab_shell_projects_path, 'fork-project',
+                                 forked_from_storage, "#{path}.git", forked_to_storage,
+                                 fork_namespace])
     end
 
     # Remove repository from file system
@@ -145,8 +144,8 @@ module Gitlab
     #   remove_repository("/path/to/storage", "gitlab/gitlab-ci")
     #
     def remove_repository(storage, name)
-      Gitlab::Utils.system_silent([gitlab_shell_projects_path,
-                                   'rm-project', storage, "#{name}.git"])
+      gitlab_shell_fast_execute([gitlab_shell_projects_path,
+                                 'rm-project', storage, "#{name}.git"])
     end
 
     # Add new key to gitlab-shell
@@ -155,8 +154,8 @@ module Gitlab
     #   add_key("key-42", "sha-rsa ...")
     #
     def add_key(key_id, key_content)
-      Gitlab::Utils.system_silent([gitlab_shell_keys_path,
-                                   'add-key', key_id, self.class.strip_key(key_content)])
+      gitlab_shell_fast_execute([gitlab_shell_keys_path,
+                                 'add-key', key_id, self.class.strip_key(key_content)])
     end
 
     # Batch-add keys to authorized_keys
@@ -175,8 +174,10 @@ module Gitlab
     #   remove_key("key-342", "sha-rsa ...")
     #
     def remove_key(key_id, key_content)
-      Gitlab::Utils.system_silent([gitlab_shell_keys_path,
-                                   'rm-key', key_id, key_content])
+      args = [gitlab_shell_keys_path, 'rm-key', key_id]
+      args << key_content if key_content
+
+      gitlab_shell_fast_execute(args)
     end
 
     # Remove all ssh keys from gitlab shell
@@ -185,7 +186,7 @@ module Gitlab
     #   remove_all_keys
     #
     def remove_all_keys
-      Gitlab::Utils.system_silent([gitlab_shell_keys_path, 'clear'])
+      gitlab_shell_fast_execute([gitlab_shell_keys_path, 'clear'])
     end
 
     # Add empty directory for storing repositories
@@ -267,5 +268,31 @@ module Gitlab
     def gitlab_shell_keys_path
       File.join(gitlab_shell_path, 'bin', 'gitlab-keys')
     end
+
+    private
+
+    def gitlab_shell_fast_execute(cmd)
+      output, status = gitlab_shell_fast_execute_helper(cmd)
+
+      return true if status.zero?
+
+      Rails.logger.error("gitlab-shell failed with error #{status}: #{output}")
+      false
+    end
+
+    def gitlab_shell_fast_execute_raise_error(cmd)
+      output, status = gitlab_shell_fast_execute_helper(cmd)
+
+      raise Error, output unless status.zero?
+      true
+    end
+
+    def gitlab_shell_fast_execute_helper(cmd)
+      vars = ENV.to_h.slice(*GITLAB_SHELL_ENV_VARS)
+
+      # Don't pass along the entire parent environment to prevent gitlab-shell
+      # from wasting I/O by searching through GEM_PATH
+      Bundler.with_original_env { Popen.popen(cmd, nil, vars) }
+    end
   end
 end
diff --git a/lib/gitlab/sherlock/line_profiler.rb b/lib/gitlab/sherlock/line_profiler.rb
index aa1468bff6bc66de1af5f493bd2fd887630c6578..b5f9d04004725e3e75c5f309a9d0d1ed3b0e4e43 100644
--- a/lib/gitlab/sherlock/line_profiler.rb
+++ b/lib/gitlab/sherlock/line_profiler.rb
@@ -77,8 +77,8 @@ module Gitlab
             line_samples << LineSample.new(duration, events)
           end
 
-          samples << FileSample.
-            new(file, line_samples, total_duration, total_events)
+          samples << FileSample
+            .new(file, line_samples, total_duration, total_events)
         end
 
         samples
diff --git a/lib/gitlab/sherlock/query.rb b/lib/gitlab/sherlock/query.rb
index 99e56e923eb08a191d3583e2baf5ab815e6d2d58..948bf5e6528730b2dca8f649c1ce972b1cc2a7e6 100644
--- a/lib/gitlab/sherlock/query.rb
+++ b/lib/gitlab/sherlock/query.rb
@@ -105,10 +105,10 @@ module Gitlab
       end
 
       def format_sql(query)
-        query.each_line.
-          map { |line| line.strip }.
-          join("\n").
-          gsub(PREFIX_NEWLINE) { "\n#{$1} " }
+        query.each_line
+          .map { |line| line.strip }
+          .join("\n")
+          .gsub(PREFIX_NEWLINE) { "\n#{$1} " }
       end
     end
   end
diff --git a/lib/gitlab/chat_commands/base_command.rb b/lib/gitlab/slash_commands/base_command.rb
similarity index 97%
rename from lib/gitlab/chat_commands/base_command.rb
rename to lib/gitlab/slash_commands/base_command.rb
index 25da8474e95a0f7c43c507b3f18be238ef849c63..cc3c9a5055527764b4a2ca8c650341ace4f9adb5 100644
--- a/lib/gitlab/chat_commands/base_command.rb
+++ b/lib/gitlab/slash_commands/base_command.rb
@@ -1,5 +1,5 @@
 module Gitlab
-  module ChatCommands
+  module SlashCommands
     class BaseCommand
       QUERY_LIMIT = 5
 
diff --git a/lib/gitlab/chat_commands/command.rb b/lib/gitlab/slash_commands/command.rb
similarity index 65%
rename from lib/gitlab/chat_commands/command.rb
rename to lib/gitlab/slash_commands/command.rb
index 3e0c30c33b7414d48b1163a355c54792f8702ae5..a78408b0519d1104e3c42f1c27d53e1a1f9256b6 100644
--- a/lib/gitlab/chat_commands/command.rb
+++ b/lib/gitlab/slash_commands/command.rb
@@ -1,11 +1,11 @@
 module Gitlab
-  module ChatCommands
+  module SlashCommands
     class Command < BaseCommand
       COMMANDS = [
-        Gitlab::ChatCommands::IssueShow,
-        Gitlab::ChatCommands::IssueNew,
-        Gitlab::ChatCommands::IssueSearch,
-        Gitlab::ChatCommands::Deploy
+        Gitlab::SlashCommands::IssueShow,
+        Gitlab::SlashCommands::IssueNew,
+        Gitlab::SlashCommands::IssueSearch,
+        Gitlab::SlashCommands::Deploy
       ].freeze
 
       def execute
@@ -15,10 +15,10 @@ module Gitlab
           if command.allowed?(project, current_user)
             command.new(project, current_user, params).execute(match)
           else
-            Gitlab::ChatCommands::Presenters::Access.new.access_denied
+            Gitlab::SlashCommands::Presenters::Access.new.access_denied
           end
         else
-          Gitlab::ChatCommands::Help.new(project, current_user, params).execute(available_commands, params[:text])
+          Gitlab::SlashCommands::Help.new(project, current_user, params).execute(available_commands, params[:text])
         end
       end
 
diff --git a/lib/gitlab/chat_commands/deploy.rb b/lib/gitlab/slash_commands/deploy.rb
similarity index 79%
rename from lib/gitlab/chat_commands/deploy.rb
rename to lib/gitlab/slash_commands/deploy.rb
index 458d90f84e8f066535b4b2790f92d82e7cd73b2c..e71eb15d60406d496d086638ca2d38f77c3ac17b 100644
--- a/lib/gitlab/chat_commands/deploy.rb
+++ b/lib/gitlab/slash_commands/deploy.rb
@@ -1,5 +1,5 @@
 module Gitlab
-  module ChatCommands
+  module SlashCommands
     class Deploy < BaseCommand
       def self.match(text)
         /\Adeploy\s+(?<from>\S+.*)\s+to+\s+(?<to>\S+.*)\z/.match(text)
@@ -24,12 +24,12 @@ module Gitlab
         actions = find_actions(from, to)
 
         if actions.none?
-          Gitlab::ChatCommands::Presenters::Deploy.new(nil).no_actions
+          Gitlab::SlashCommands::Presenters::Deploy.new(nil).no_actions
         elsif actions.one?
           action = play!(from, to, actions.first)
-          Gitlab::ChatCommands::Presenters::Deploy.new(action).present(from, to)
+          Gitlab::SlashCommands::Presenters::Deploy.new(action).present(from, to)
         else
-          Gitlab::ChatCommands::Presenters::Deploy.new(actions).too_many_actions
+          Gitlab::SlashCommands::Presenters::Deploy.new(actions).too_many_actions
         end
       end
 
diff --git a/lib/gitlab/chat_commands/help.rb b/lib/gitlab/slash_commands/help.rb
similarity index 82%
rename from lib/gitlab/chat_commands/help.rb
rename to lib/gitlab/slash_commands/help.rb
index 6c0e4d304a4f79f6f9943b8d61a3dd953895512d..81f3707e03e4a735ed1dcc664c68f9f05bf84f57 100644
--- a/lib/gitlab/chat_commands/help.rb
+++ b/lib/gitlab/slash_commands/help.rb
@@ -1,5 +1,5 @@
 module Gitlab
-  module ChatCommands
+  module SlashCommands
     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
@@ -17,7 +17,7 @@ module Gitlab
       end
 
       def execute(commands, text)
-        Gitlab::ChatCommands::Presenters::Help.new(commands).present(trigger, text)
+        Gitlab::SlashCommands::Presenters::Help.new(commands).present(trigger, text)
       end
 
       def trigger
diff --git a/lib/gitlab/chat_commands/issue_command.rb b/lib/gitlab/slash_commands/issue_command.rb
similarity index 92%
rename from lib/gitlab/chat_commands/issue_command.rb
rename to lib/gitlab/slash_commands/issue_command.rb
index 84de3e44c70c8c3ea28f26ddc7f7748783db6304..87ea19b880619eabbc57cd7521f071280d5b52ef 100644
--- a/lib/gitlab/chat_commands/issue_command.rb
+++ b/lib/gitlab/slash_commands/issue_command.rb
@@ -1,5 +1,5 @@
 module Gitlab
-  module ChatCommands
+  module SlashCommands
     class IssueCommand < BaseCommand
       def self.available?(project)
         project.issues_enabled? && project.default_issues_tracker?
diff --git a/lib/gitlab/chat_commands/issue_new.rb b/lib/gitlab/slash_commands/issue_new.rb
similarity index 92%
rename from lib/gitlab/chat_commands/issue_new.rb
rename to lib/gitlab/slash_commands/issue_new.rb
index 016054ecd465e73aea59d3954663c20a67a11678..25f965e843d76bf229ca06e50aba70cbfdb9e37b 100644
--- a/lib/gitlab/chat_commands/issue_new.rb
+++ b/lib/gitlab/slash_commands/issue_new.rb
@@ -1,5 +1,5 @@
 module Gitlab
-  module ChatCommands
+  module SlashCommands
     class IssueNew < IssueCommand
       def self.match(text)
         # we can not match \n with the dot by passing the m modifier as than
@@ -35,7 +35,7 @@ module Gitlab
       end
 
       def presenter(issue)
-        Gitlab::ChatCommands::Presenters::IssueNew.new(issue)
+        Gitlab::SlashCommands::Presenters::IssueNew.new(issue)
       end
     end
   end
diff --git a/lib/gitlab/chat_commands/issue_search.rb b/lib/gitlab/slash_commands/issue_search.rb
similarity index 95%
rename from lib/gitlab/chat_commands/issue_search.rb
rename to lib/gitlab/slash_commands/issue_search.rb
index 3491b53093ecf6db66fd9f19483520b213bfd51c..acba84b54b4ce5476debbe1fc2fd2b2922063fb1 100644
--- a/lib/gitlab/chat_commands/issue_search.rb
+++ b/lib/gitlab/slash_commands/issue_search.rb
@@ -1,5 +1,5 @@
 module Gitlab
-  module ChatCommands
+  module SlashCommands
     class IssueSearch < IssueCommand
       def self.match(text)
         /\Aissue\s+search\s+(?<query>.*)/.match(text)
diff --git a/lib/gitlab/chat_commands/issue_show.rb b/lib/gitlab/slash_commands/issue_show.rb
similarity index 69%
rename from lib/gitlab/chat_commands/issue_show.rb
rename to lib/gitlab/slash_commands/issue_show.rb
index d6013f4d10cb0fdef8deb5b29bb0b46b8ddecffa..ffa5184e5cb6872ff4078caa01e32df23efb848e 100644
--- a/lib/gitlab/chat_commands/issue_show.rb
+++ b/lib/gitlab/slash_commands/issue_show.rb
@@ -1,5 +1,5 @@
 module Gitlab
-  module ChatCommands
+  module SlashCommands
     class IssueShow < IssueCommand
       def self.match(text)
         /\Aissue\s+show\s+#{Issue.reference_prefix}?(?<iid>\d+)/.match(text)
@@ -13,9 +13,9 @@ module Gitlab
         issue = find_by_iid(match[:iid])
 
         if issue
-          Gitlab::ChatCommands::Presenters::IssueShow.new(issue).present
+          Gitlab::SlashCommands::Presenters::IssueShow.new(issue).present
         else
-          Gitlab::ChatCommands::Presenters::Access.new.not_found
+          Gitlab::SlashCommands::Presenters::Access.new.not_found
         end
       end
     end
diff --git a/lib/gitlab/chat_commands/presenters/access.rb b/lib/gitlab/slash_commands/presenters/access.rb
similarity index 98%
rename from lib/gitlab/chat_commands/presenters/access.rb
rename to lib/gitlab/slash_commands/presenters/access.rb
index 92f4fa17f78d32a66b04a9bd116382304a11c1c3..1a817eb735b913f3cf1dc6e515aaeca006c7fad5 100644
--- a/lib/gitlab/chat_commands/presenters/access.rb
+++ b/lib/gitlab/slash_commands/presenters/access.rb
@@ -1,5 +1,5 @@
 module Gitlab
-  module ChatCommands
+  module SlashCommands
     module Presenters
       class Access < Presenters::Base
         def access_denied
diff --git a/lib/gitlab/chat_commands/presenters/base.rb b/lib/gitlab/slash_commands/presenters/base.rb
similarity index 98%
rename from lib/gitlab/chat_commands/presenters/base.rb
rename to lib/gitlab/slash_commands/presenters/base.rb
index 05994bee79decf609dd2f3f258cfe3abb0e1d73d..27696436574e2230252d3474a86ab8559e10fada 100644
--- a/lib/gitlab/chat_commands/presenters/base.rb
+++ b/lib/gitlab/slash_commands/presenters/base.rb
@@ -1,5 +1,5 @@
 module Gitlab
-  module ChatCommands
+  module SlashCommands
     module Presenters
       class Base
         include Gitlab::Routing.url_helpers
diff --git a/lib/gitlab/chat_commands/presenters/deploy.rb b/lib/gitlab/slash_commands/presenters/deploy.rb
similarity index 95%
rename from lib/gitlab/chat_commands/presenters/deploy.rb
rename to lib/gitlab/slash_commands/presenters/deploy.rb
index 863d0bf99ca2fc9f3216d3c2a668b9efeda41031..b8dc77bd37bc0403836946531f774c150629d7b7 100644
--- a/lib/gitlab/chat_commands/presenters/deploy.rb
+++ b/lib/gitlab/slash_commands/presenters/deploy.rb
@@ -1,5 +1,5 @@
 module Gitlab
-  module ChatCommands
+  module SlashCommands
     module Presenters
       class Deploy < Presenters::Base
         def present(from, to)
diff --git a/lib/gitlab/chat_commands/presenters/help.rb b/lib/gitlab/slash_commands/presenters/help.rb
similarity index 96%
rename from lib/gitlab/chat_commands/presenters/help.rb
rename to lib/gitlab/slash_commands/presenters/help.rb
index cd47b7f4c6ab2fa4559f2868e3abd7a7d16c0039..ea611a4d629b81ac24c5fecbb94d8f7203fc7508 100644
--- a/lib/gitlab/chat_commands/presenters/help.rb
+++ b/lib/gitlab/slash_commands/presenters/help.rb
@@ -1,5 +1,5 @@
 module Gitlab
-  module ChatCommands
+  module SlashCommands
     module Presenters
       class Help < Presenters::Base
         def present(trigger, text)
diff --git a/lib/gitlab/chat_commands/presenters/issue_base.rb b/lib/gitlab/slash_commands/presenters/issue_base.rb
similarity index 97%
rename from lib/gitlab/chat_commands/presenters/issue_base.rb
rename to lib/gitlab/slash_commands/presenters/issue_base.rb
index 25bc82994baa5095a7df469422dd0ea4a32daf93..341f2aabdd0968ab1d15b2f86b6d79839217d0fe 100644
--- a/lib/gitlab/chat_commands/presenters/issue_base.rb
+++ b/lib/gitlab/slash_commands/presenters/issue_base.rb
@@ -1,5 +1,5 @@
 module Gitlab
-  module ChatCommands
+  module SlashCommands
     module Presenters
       module IssueBase
         def color(issuable)
diff --git a/lib/gitlab/chat_commands/presenters/issue_new.rb b/lib/gitlab/slash_commands/presenters/issue_new.rb
similarity index 98%
rename from lib/gitlab/chat_commands/presenters/issue_new.rb
rename to lib/gitlab/slash_commands/presenters/issue_new.rb
index 3674ba25641e4dcda131059bf2ef14eb3679ae19..86490a39cc183e96789822ea97d97cd8d0d8a6dc 100644
--- a/lib/gitlab/chat_commands/presenters/issue_new.rb
+++ b/lib/gitlab/slash_commands/presenters/issue_new.rb
@@ -1,5 +1,5 @@
 module Gitlab
-  module ChatCommands
+  module SlashCommands
     module Presenters
       class IssueNew < Presenters::Base
         include Presenters::IssueBase
diff --git a/lib/gitlab/chat_commands/presenters/issue_search.rb b/lib/gitlab/slash_commands/presenters/issue_search.rb
similarity index 98%
rename from lib/gitlab/chat_commands/presenters/issue_search.rb
rename to lib/gitlab/slash_commands/presenters/issue_search.rb
index 73788cf96626a27d1b21dc6ba48f64e3b59ae37a..4e27d668685e1ae0c79e38bd69bdfcfb8205b20e 100644
--- a/lib/gitlab/chat_commands/presenters/issue_search.rb
+++ b/lib/gitlab/slash_commands/presenters/issue_search.rb
@@ -1,5 +1,5 @@
 module Gitlab
-  module ChatCommands
+  module SlashCommands
     module Presenters
       class IssueSearch < Presenters::Base
         include Presenters::IssueBase
diff --git a/lib/gitlab/chat_commands/presenters/issue_show.rb b/lib/gitlab/slash_commands/presenters/issue_show.rb
similarity index 98%
rename from lib/gitlab/chat_commands/presenters/issue_show.rb
rename to lib/gitlab/slash_commands/presenters/issue_show.rb
index bd784ad241eb36fd4a662c5e99c339207dd0df35..c99316df66701ee51cdfa4db4ea1df61c4dd764d 100644
--- a/lib/gitlab/chat_commands/presenters/issue_show.rb
+++ b/lib/gitlab/slash_commands/presenters/issue_show.rb
@@ -1,5 +1,5 @@
 module Gitlab
-  module ChatCommands
+  module SlashCommands
     module Presenters
       class IssueShow < Presenters::Base
         include Presenters::IssueBase
diff --git a/lib/gitlab/chat_commands/result.rb b/lib/gitlab/slash_commands/result.rb
similarity index 73%
rename from lib/gitlab/chat_commands/result.rb
rename to lib/gitlab/slash_commands/result.rb
index 324d7ef43a3bc30d5165770d326fe7447699b195..7021b4b01b2b20254fdd7902a1a5cae151872a5c 100644
--- a/lib/gitlab/chat_commands/result.rb
+++ b/lib/gitlab/slash_commands/result.rb
@@ -1,5 +1,5 @@
 module Gitlab
-  module ChatCommands
+  module SlashCommands
     Result = Struct.new(:type, :message)
   end
 end
diff --git a/lib/gitlab/sql/recursive_cte.rb b/lib/gitlab/sql/recursive_cte.rb
index 5b1b03820a328ff59cd7a60b4bfe855efcee5178..16ec002f1397128f7efb66d758a857ed9a841d86 100644
--- a/lib/gitlab/sql/recursive_cte.rb
+++ b/lib/gitlab/sql/recursive_cte.rb
@@ -52,10 +52,10 @@ module Gitlab
       # Applies the CTE to the given relation, returning a new one that will
       # query from it.
       def apply_to(relation)
-        relation.except(:where).
-          with.
-          recursive(to_arel).
-          from(alias_to(relation.model.arel_table))
+        relation.except(:where)
+          .with
+          .recursive(to_arel)
+          .from(alias_to(relation.model.arel_table))
       end
     end
   end
diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb
index 23af9318d1ace37a14187d17c5271e3d42675c69..073af685a09da571329b4493d2d79ca9674e1761 100644
--- a/lib/gitlab/url_builder.rb
+++ b/lib/gitlab/url_builder.rb
@@ -23,9 +23,9 @@ module Gitlab
       when WikiPage
         wiki_page_url
       when ProjectSnippet
-        project_snippet_url(object)
+        project_snippet_url(object.project, object)
       when Snippet
-        personal_snippet_url(object)
+        snippet_url(object)
       else
         raise NotImplementedError.new("No URL builder defined for #{object.class}")
       end
@@ -65,13 +65,13 @@ module Gitlab
         if snippet.is_a?(PersonalSnippet)
           snippet_url(snippet, anchor: dom_id(object))
         else
-          project_snippet_url(snippet, anchor: dom_id(object))
+          project_snippet_url(snippet.project, snippet, anchor: dom_id(object))
         end
       end
     end
 
     def wiki_page_url
-      namespace_project_wiki_url(object.wiki.project.namespace, object.wiki.project, object.slug)
+      project_wiki_url(object.wiki.project, object.slug)
     end
   end
 end
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index bcba2e3e1b6bb2055fbebce510288b3997b1da8a..f19b325a1263a009dd5e475218d8989521133a89 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -20,13 +20,15 @@ module Gitlab
           counts: {
             boards: Board.count,
             ci_builds: ::Ci::Build.count,
-            ci_pipelines: ::Ci::Pipeline.count,
+            ci_internal_pipelines: ::Ci::Pipeline.internal.count,
+            ci_external_pipelines: ::Ci::Pipeline.external.count,
             ci_runners: ::Ci::Runner.count,
             ci_triggers: ::Ci::Trigger.count,
             ci_pipeline_schedules: ::Ci::PipelineSchedule.count,
             deploy_keys: DeployKey.count,
             deployments: Deployment.count,
             environments: Environment.count,
+            in_review_folder: Environment.in_review_folder.count,
             groups: Group.count,
             issues: Issue.count,
             keys: Key.count,
diff --git a/lib/gitlab/view/presenter/base.rb b/lib/gitlab/view/presenter/base.rb
index dbfe0941e4d5390a71c1264b3b22415f4bf416bc..841fb681435533a9d2290acd79d4486dbc65316e 100644
--- a/lib/gitlab/view/presenter/base.rb
+++ b/lib/gitlab/view/presenter/base.rb
@@ -15,6 +15,11 @@ module Gitlab
           super(user, action, overriden_subject || subject)
         end
 
+        # delegate all #can? queries to the subject
+        def declarative_policy_delegate
+          subject
+        end
+
         class_methods do
           def presenter?
             true
diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb
index 2b53798e70f10be86fbcb7860a7ab7cff7a94f76..48f3d95077928f7509f919238113f6f51c09996f 100644
--- a/lib/gitlab/visibility_level.rb
+++ b/lib/gitlab/visibility_level.rb
@@ -13,18 +13,8 @@ module Gitlab
       scope :public_and_internal_only,  -> { where(visibility_level: [PUBLIC, INTERNAL] ) }
       scope :non_public_only,           -> { where.not(visibility_level: PUBLIC) }
 
-      scope :public_to_user, -> (user) do
-        if user
-          if user.admin?
-            all
-          elsif !user.external?
-            public_and_internal_only
-          else
-            public_only
-          end
-        else
-          public_only
-        end
+      scope :public_to_user, -> (user = nil) do
+        where(visibility_level: VisibilityLevel.levels_for_user(user))
       end
     end
 
@@ -35,6 +25,18 @@ module Gitlab
     class << self
       delegate :values, to: :options
 
+      def levels_for_user(user = nil)
+        return [PUBLIC] unless user
+
+        if user.full_private_access?
+          [PRIVATE, INTERNAL, PUBLIC]
+        elsif user.external?
+          [PUBLIC]
+        else
+          [INTERNAL, PUBLIC]
+        end
+      end
+
       def string_values
         string_options.keys
       end
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index 7f27317775c0d6b130c946355e8b2bf721ae7de5..f96ee69096d4018d4bb7cb14cb81d394048484b9 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -26,7 +26,10 @@ module Gitlab
         }
 
         if Gitlab.config.gitaly.enabled
-          address = Gitlab::GitalyClient.address(project.repository_storage)
+          server = {
+            address: Gitlab::GitalyClient.address(project.repository_storage),
+            token: Gitlab::GitalyClient.token(project.repository_storage)
+          }
           params[:Repository] = repository.gitaly_repository.to_h
 
           feature_enabled = case action.to_s
@@ -39,8 +42,10 @@ module Gitlab
                             else
                               raise "Unsupported action: #{action}"
                             end
-
-          params[:GitalyAddress] = address if feature_enabled
+          if feature_enabled
+            params[:GitalyAddress] = server[:address] # This field will be deprecated
+            params[:GitalyServer] = server
+          end
         end
 
         params
diff --git a/lib/system_check/simple_executor.rb b/lib/system_check/simple_executor.rb
index dc2d4643a011417f82bf80c409945c0f89035052..e5986612908e910e6967b3a19d60f391153b5fbd 100644
--- a/lib/system_check/simple_executor.rb
+++ b/lib/system_check/simple_executor.rb
@@ -75,6 +75,8 @@ module SystemCheck
 
         check.show_error
       end
+    rescue StandardError => e
+      $stdout.puts "Exception: #{e.message}".color(:red)
     end
 
     private
diff --git a/lib/tasks/gitlab/gitaly.rake b/lib/tasks/gitlab/gitaly.rake
index e88111c37251606b506b0803f07019d9a47e31e7..a8db5701d0b95dc05adc1e67ab173b066dd16138 100644
--- a/lib/tasks/gitlab/gitaly.rake
+++ b/lib/tasks/gitlab/gitaly.rake
@@ -58,8 +58,9 @@ namespace :gitlab do
 
         storages << { name: key, path: val['path'] }
       end
-
-      TOML.dump(socket_path: address.sub(%r{\Aunix:}, ''), storage: storages)
+      config = { socket_path: address.sub(%r{\Aunix:}, ''), storage: storages }
+      config[:auth] = { token: 'secret' } if Rails.env.test?
+      TOML.dump(config)
     end
 
     def create_gitaly_configuration
diff --git a/lib/tasks/migrate/add_limits_mysql.rake b/lib/tasks/migrate/add_limits_mysql.rake
index 761f275d42ac1f8173af9ff25f72c0a1b72c7f51..151f42a222294234170087630dc2431f995b37bc 100644
--- a/lib/tasks/migrate/add_limits_mysql.rake
+++ b/lib/tasks/migrate/add_limits_mysql.rake
@@ -1,9 +1,11 @@
 require Rails.root.join('db/migrate/limits_to_mysql')
 require Rails.root.join('db/migrate/markdown_cache_limits_to_mysql')
+require Rails.root.join('db/migrate/merge_request_diff_file_limits_to_mysql')
 
 desc "GitLab | Add limits to strings in mysql database"
 task add_limits_mysql: :environment do
   puts "Adding limits to schema.rb for mysql"
   LimitsToMysql.new.up
   MarkdownCacheLimitsToMysql.new.up
+  MergeRequestDiffFileLimitsToMysql.new.up
 end
diff --git a/locale/bg/gitlab.po b/locale/bg/gitlab.po
index e6caf83252d75fa4f4f36103c40c303942a14c34..db4dc9a02da701c6ce1740f7dbf6642bd3bcf6ea 100644
--- a/locale/bg/gitlab.po
+++ b/locale/bg/gitlab.po
@@ -1,27 +1,274 @@
+# Huang Tao <htve@outlook.com>, 2017. #zanata
 # Lyubomir Vasilev <lyubomirv@abv.bg>, 2017. #zanata
 msgid ""
 msgstr ""
 "Project-Id-Version: gitlab 1.0.0\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-05-04 19:24-0500\n"
+"POT-Creation-Date: 2017-06-19 15:50-0500\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
-"PO-Revision-Date: 2017-06-05 09:40-0400\n"
+"PO-Revision-Date: 2017-06-23 04:07-0400\n"
 "Last-Translator: Lyubomir Vasilev <lyubomirv@abv.bg>\n"
-"Language-Team: Bulgarian\n"
+"Language-Team: Bulgarian (https://translate.zanata.org/project/view/GitLab)\n"
 "Language: bg\n"
 "X-Generator: Zanata 3.9.6\n"
 "Plural-Forms: nplurals=2; plural=(n != 1)\n"
 
+msgid "%d additional commit has been omitted to prevent performance issues."
+msgid_plural ""
+"%d additional commits have been omitted to prevent performance issues."
+msgstr[0] "%d подаване беше пропуснато, за да не се натоварва системата."
+msgstr[1] "%d подавания бяха пропуснати, за да не се натоварва системата."
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] "%d подаване"
+msgstr[1] "%d подавания"
+
+msgid "%{commit_author_link} committed %{commit_timeago}"
+msgstr "%{commit_author_link} подаде %{commit_timeago}"
+
+msgid "About auto deploy"
+msgstr "Относно автоматичното внедряване"
+
+msgid "Active"
+msgstr "Активно"
+
+msgid "Activity"
+msgstr "Дейност"
+
+msgid "Add Changelog"
+msgstr "Добавяне на списък с промени"
+
+msgid "Add Contribution guide"
+msgstr "Добавяне на ръководство за сътрудничество"
+
+msgid "Add License"
+msgstr "Добавяне на лиценз"
+
+msgid "Add an SSH key to your profile to pull or push via SSH."
+msgstr ""
+"Добавете SSH ключ в профила си, за да можете да изтегляте или изпращате "
+"промени чрез SSH."
+
+msgid "Add new directory"
+msgstr "Добавяне на нова папка"
+
+msgid "Archived project! Repository is read-only"
+msgstr "Архивиран проект! Хранилището е само за четене"
+
+msgid "Are you sure you want to delete this pipeline schedule?"
+msgstr "Наистина ли искате да изтриете този план за схема?"
+
+msgid "Attach a file by drag &amp; drop or %{upload_link}"
+msgstr "Прикачете файл чрез влачене и пускане или %{upload_link}"
+
+msgid "Branch"
+msgid_plural "Branches"
+msgstr[0] "Клон"
+msgstr[1] "Клонове"
+
+msgid ""
+"Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, "
+"choose a GitLab CI Yaml template and commit your changes. "
+"%{link_to_autodeploy_doc}"
+msgstr ""
+"Клонът <strong>%{branch_name}</strong> беше създаден. За да настроите "
+"автоматичното внедряване, изберете Yaml шаблон за GitLab CI и подайте "
+"промените си. %{link_to_autodeploy_doc}"
+
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr "Търсете в клоновете"
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr "Превключване на клона"
+
+msgid "Branches"
+msgstr "Клонове"
+
+msgid "Browse Directory"
+msgstr "Преглед на папката"
+
+msgid "Browse File"
+msgstr "Преглед на файла"
+
+msgid "Browse Files"
+msgstr "Преглед на файловете"
+
+msgid "Browse files"
+msgstr "Разглеждане на файловете"
+
 msgid "ByAuthor|by"
 msgstr "от"
 
+msgid "CI configuration"
+msgstr "Конфигурация на непрекъсната интеграция"
+
+msgid "Cancel"
+msgstr "Отказ"
+
+msgid "ChangeTypeActionLabel|Pick into branch"
+msgstr "Избиране в клона"
+
+msgid "ChangeTypeActionLabel|Revert in branch"
+msgstr "Отмяна в клона"
+
+msgid "ChangeTypeAction|Cherry-pick"
+msgstr "Подбиране"
+
+msgid "ChangeTypeAction|Revert"
+msgstr "Отмяна"
+
+msgid "Changelog"
+msgstr "Списък с промени"
+
+msgid "Charts"
+msgstr "Графики"
+
+msgid "Cherry-pick this commit"
+msgstr "Подбиране на това подаване"
+
+msgid "Cherry-pick this merge request"
+msgstr "Подбиране на тази заявка за сливане"
+
+msgid "CiStatusLabel|canceled"
+msgstr "отказано"
+
+msgid "CiStatusLabel|created"
+msgstr "създадено"
+
+msgid "CiStatusLabel|failed"
+msgstr "неуспешно"
+
+msgid "CiStatusLabel|manual action"
+msgstr "ръчно действие"
+
+msgid "CiStatusLabel|passed"
+msgstr "успешно"
+
+msgid "CiStatusLabel|passed with warnings"
+msgstr "успешно, с предупреждения"
+
+msgid "CiStatusLabel|pending"
+msgstr "на изчакване"
+
+msgid "CiStatusLabel|skipped"
+msgstr "пропуснато"
+
+msgid "CiStatusLabel|waiting for manual action"
+msgstr "чакане за ръчно действие"
+
+msgid "CiStatusText|blocked"
+msgstr "блокирано"
+
+msgid "CiStatusText|canceled"
+msgstr "отказано"
+
+msgid "CiStatusText|created"
+msgstr "създадено"
+
+msgid "CiStatusText|failed"
+msgstr "неуспешно"
+
+msgid "CiStatusText|manual"
+msgstr "ръчно"
+
+msgid "CiStatusText|passed"
+msgstr "успешно"
+
+msgid "CiStatusText|pending"
+msgstr "на изчакване"
+
+msgid "CiStatusText|skipped"
+msgstr "пропуснато"
+
+msgid "CiStatus|running"
+msgstr "протича в момента"
+
 msgid "Commit"
 msgid_plural "Commits"
 msgstr[0] "Подаване"
 msgstr[1] "Подавания"
 
+msgid "Commit message"
+msgstr "Съобщение за подаването"
+
+msgid "CommitBoxTitle|Commit"
+msgstr "Подаване"
+
+msgid "CommitMessage|Add %{file_name}"
+msgstr "Добавяне на „%{file_name}“"
+
+msgid "Commits"
+msgstr "Подавания"
+
+msgid "Commits feed"
+msgstr "Поток от подавания"
+
+msgid "Commits|History"
+msgstr "История"
+
+msgid "Committed by"
+msgstr "Подадено от"
+
+msgid "Compare"
+msgstr "Сравнение"
+
+msgid "Contribution guide"
+msgstr "Ръководство за сътрудничество"
+
+msgid "Contributors"
+msgstr "Сътрудници"
+
+msgid "Copy URL to clipboard"
+msgstr "Копиране на адреса в буфера за обмен"
+
+msgid "Copy commit SHA to clipboard"
+msgstr "Копиране на идентификатора на подаването в буфера за обмен"
+
+msgid "Create New Directory"
+msgstr "Създаване на нова папка"
+
+msgid "Create directory"
+msgstr "Създаване на папка"
+
+msgid "Create empty bare repository"
+msgstr "Създаване на празно хранилище"
+
+msgid "Create merge request"
+msgstr "Създаване на заявка за сливане"
+
+msgid "Create new..."
+msgstr "Създаване на нов…"
+
+msgid "CreateNewFork|Fork"
+msgstr "Разклоняване"
+
+msgid "CreateTag|Tag"
+msgstr "Етикет"
+
+msgid "Cron Timezone"
+msgstr "Часова зона за „Cron“"
+
+msgid "Cron syntax"
+msgstr "Синтаксис на „Cron“"
+
+msgid "Custom notification events"
+msgstr "Персонализирани събития за известяване"
+
+msgid ""
+"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 %{notification_link}."
+msgstr ""
+"Персонализираните нива на известяване са същите като нивата за участие. С "
+"персонализираните нива на известяване ще можете да получавате и известия за "
+"избрани събития. За да научите повече, прегледайте %{notification_link}."
+
+msgid "Cycle Analytics"
+msgstr "Анализ на циклите"
+
 msgid ""
 "Cycle Analytics gives an overview of how much time it takes to go from idea "
 "to production in your project."
@@ -50,17 +297,100 @@ msgstr "Подготовка за издаване"
 msgid "CycleAnalyticsStage|Test"
 msgstr "Тестване"
 
+msgid "Define a custom pattern with cron syntax"
+msgstr "Задайте потребителски шаблон, използвайки синтаксиса на „Cron“"
+
+msgid "Delete"
+msgstr "Изтриване"
+
 msgid "Deploy"
 msgid_plural "Deploys"
 msgstr[0] "Внедряване"
 msgstr[1] "Внедрявания"
 
+msgid "Description"
+msgstr "Описание"
+
+msgid "Directory name"
+msgstr "Име на папката"
+
+msgid "Don't show again"
+msgstr "Да не се показва повече"
+
+msgid "Download"
+msgstr "Сваляне"
+
+msgid "Download tar"
+msgstr "Сваляне във формат „tar“"
+
+msgid "Download tar.bz2"
+msgstr "Сваляне във формат „tar.bz2“"
+
+msgid "Download tar.gz"
+msgstr "Сваляне във формат „tar.gz“"
+
+msgid "Download zip"
+msgstr "Сваляне във формат „zip“"
+
+msgid "DownloadArtifacts|Download"
+msgstr "Сваляне"
+
+msgid "DownloadCommit|Email Patches"
+msgstr "Изпращане на кръпките по е-поща"
+
+msgid "DownloadCommit|Plain Diff"
+msgstr "Обикновен файл с разлики"
+
+msgid "DownloadSource|Download"
+msgstr "Сваляне"
+
+msgid "Edit"
+msgstr "Редактиране"
+
+msgid "Edit Pipeline Schedule %{id}"
+msgstr "Редактиране на плана %{id} за схема"
+
+msgid "Every day (at 4:00am)"
+msgstr "Всеки ден (в 4 ч. сутринта)"
+
+msgid "Every month (on the 1st at 4:00am)"
+msgstr "Всеки месец (на 1-во число, в 4 ч. сутринта)"
+
+msgid "Every week (Sundays at 4:00am)"
+msgstr "Всяка седмица (в неделя, в 4 ч. сутринта)"
+
+msgid "Failed to change the owner"
+msgstr "Собственикът не може да бъде променен"
+
+msgid "Failed to remove the pipeline schedule"
+msgstr "Планът за схема не може да бъде премахнат"
+
+msgid "Files"
+msgstr "Файлове"
+
+msgid "Filter by commit message"
+msgstr "Филтриране по съобщение"
+
+msgid "Find by path"
+msgstr "Търсене по път"
+
+msgid "Find file"
+msgstr "Търсене на файл"
+
 msgid "FirstPushedBy|First"
 msgstr "Първо"
 
 msgid "FirstPushedBy|pushed by"
 msgstr "изпращане на промени от"
 
+msgid "Fork"
+msgid_plural "Forks"
+msgstr[0] "Разклонение"
+msgstr[1] "Разклонения"
+
+msgid "ForkedFromProjectPath|Forked from"
+msgstr "Разклонение на"
+
 msgid "From issue creation until deploy to production"
 msgstr "От създаването на проблема до внедряването в крайната версия"
 
@@ -68,50 +398,302 @@ msgid "From merge request merge until deploy to production"
 msgstr ""
 "От прилагането на заявката за сливане до внедряването в крайната версия"
 
+msgid "Go to your fork"
+msgstr "Към Вашето разклонение"
+
+msgid "GoToYourFork|Fork"
+msgstr "Разклонение"
+
+msgid "Home"
+msgstr "Начало"
+
+msgid "Housekeeping successfully started"
+msgstr "Освежаването започна успешно"
+
+msgid "Import repository"
+msgstr "Внасяне на хранилище"
+
+msgid "Interval Pattern"
+msgstr "Шаблон за интервала"
+
 msgid "Introducing Cycle Analytics"
-msgstr "Представяме Ви анализът на циклите"
+msgstr "Представяме Ви анализа на циклите"
+
+msgid "LFSStatus|Disabled"
+msgstr "Изключено"
+
+msgid "LFSStatus|Enabled"
+msgstr "Включено"
 
 msgid "Last %d day"
 msgid_plural "Last %d days"
 msgstr[0] "Последния %d ден"
 msgstr[1] "Последните %d дни"
 
+msgid "Last Pipeline"
+msgstr "Последна схема"
+
+msgid "Last Update"
+msgstr "Последна промяна"
+
+msgid "Last commit"
+msgstr "Последно подаване"
+
+msgid "Learn more in the"
+msgstr "Научете повече в"
+
+msgid "Learn more in the|pipeline schedules documentation"
+msgstr "документацията относно планирането на схеми"
+
+msgid "Leave group"
+msgstr "Напускане на групата"
+
+msgid "Leave project"
+msgstr "Напускане на проекта"
+
 msgid "Limited to showing %d event at most"
 msgid_plural "Limited to showing %d events at most"
-msgstr[0] "Ограничено до показване на последното %d събитие"
-msgstr[1] "Ограничено до показване на последните %d събития"
+msgstr[0] "Ограничено до показване на най-много %d събитие"
+msgstr[1] "Ограничено до показване на най-много %d събития"
 
 msgid "Median"
 msgstr "Медиана"
 
+msgid "MissingSSHKeyWarningLink|add an SSH key"
+msgstr "добавите SSH ключ"
+
 msgid "New Issue"
 msgid_plural "New Issues"
 msgstr[0] "Нов проблем"
 msgstr[1] "Нови проблема"
 
+msgid "New Pipeline Schedule"
+msgstr "Нов план за схема"
+
+msgid "New branch"
+msgstr "Нов клон"
+
+msgid "New directory"
+msgstr "Нова папка"
+
+msgid "New file"
+msgstr "Нов файл"
+
+msgid "New issue"
+msgstr "Нов проблем"
+
+msgid "New merge request"
+msgstr "Нова заявка за сливане"
+
+msgid "New schedule"
+msgstr "Нов план"
+
+msgid "New snippet"
+msgstr "Нов отрязък"
+
+msgid "New tag"
+msgstr "Нов етикет"
+
+msgid "No repository"
+msgstr "Няма хранилище"
+
+msgid "No schedules"
+msgstr "Няма планове"
+
 msgid "Not available"
 msgstr "Не е налично"
 
 msgid "Not enough data"
 msgstr "Няма достатъчно данни"
 
+msgid "Notification events"
+msgstr "Събития за известяване"
+
+msgid "NotificationEvent|Close issue"
+msgstr "Затваряне на проблем"
+
+msgid "NotificationEvent|Close merge request"
+msgstr "Затваряне на заявка за сливане"
+
+msgid "NotificationEvent|Failed pipeline"
+msgstr "Неуспешно изпълнение на схема"
+
+msgid "NotificationEvent|Merge merge request"
+msgstr "Прилагане на заявка за сливане"
+
+msgid "NotificationEvent|New issue"
+msgstr "Нов проблем"
+
+msgid "NotificationEvent|New merge request"
+msgstr "Нова заявка за сливане"
+
+msgid "NotificationEvent|New note"
+msgstr "Нова бележка"
+
+msgid "NotificationEvent|Reassign issue"
+msgstr "Преназначаване на проблем"
+
+msgid "NotificationEvent|Reassign merge request"
+msgstr "Преназначаване на заявка за сливане"
+
+msgid "NotificationEvent|Reopen issue"
+msgstr "Повторно отваряне на проблем"
+
+msgid "NotificationEvent|Successful pipeline"
+msgstr "Успешно изпълнение на схема"
+
+msgid "NotificationLevel|Custom"
+msgstr "Персонализирани"
+
+msgid "NotificationLevel|Disabled"
+msgstr "Изключени"
+
+msgid "NotificationLevel|Global"
+msgstr "Глобални"
+
+msgid "NotificationLevel|On mention"
+msgstr "При споменаване"
+
+msgid "NotificationLevel|Participate"
+msgstr "Участие"
+
+msgid "NotificationLevel|Watch"
+msgstr "Наблюдение"
+
+msgid "OfSearchInADropdown|Filter"
+msgstr "Филтър"
+
 msgid "OpenedNDaysAgo|Opened"
 msgstr "Отворен"
 
+msgid "Options"
+msgstr "Опции"
+
+msgid "Owner"
+msgstr "Собственик"
+
+msgid "Pipeline"
+msgstr "Схема"
+
 msgid "Pipeline Health"
 msgstr "Състояние"
 
+msgid "Pipeline Schedule"
+msgstr "План за схема"
+
+msgid "Pipeline Schedules"
+msgstr "Планове за схема"
+
+msgid "PipelineSchedules|Activated"
+msgstr "Включено"
+
+msgid "PipelineSchedules|Active"
+msgstr "Активно"
+
+msgid "PipelineSchedules|All"
+msgstr "Всички"
+
+msgid "PipelineSchedules|Inactive"
+msgstr "Неактивно"
+
+msgid "PipelineSchedules|Next Run"
+msgstr "Следващо изпълнение"
+
+msgid "PipelineSchedules|None"
+msgstr "Нищо"
+
+msgid "PipelineSchedules|Provide a short description for this pipeline"
+msgstr "Въведете кратко описание за тази схема"
+
+msgid "PipelineSchedules|Take ownership"
+msgstr "Поемане на собствеността"
+
+msgid "PipelineSchedules|Target"
+msgstr "Цел"
+
+msgid "PipelineSheduleIntervalPattern|Custom"
+msgstr "собствен"
+
+msgid "Pipeline|with stage"
+msgstr "с етап"
+
+msgid "Pipeline|with stages"
+msgstr "с етапи"
+
+msgid "Project '%{project_name}' queued for deletion."
+msgstr "Проектът „%{project_name}“ е добавен в опашката за изтриване."
+
+msgid "Project '%{project_name}' was successfully created."
+msgstr "Проектът „%{project_name}“ беше създаден успешно."
+
+msgid "Project '%{project_name}' was successfully updated."
+msgstr "Проектът „%{project_name}“ беше обновен успешно."
+
+msgid "Project '%{project_name}' will be deleted."
+msgstr "Проектът „%{project_name}“ ще бъде изтрит."
+
+msgid "Project access must be granted explicitly to each user."
+msgstr ""
+"Достъпът до проекта трябва да бъде даван поотделно на всеки потребител."
+
+msgid "Project export could not be deleted."
+msgstr "Изнесените данни на проекта не могат да бъдат изтрити."
+
+msgid "Project export has been deleted."
+msgstr "Изнесените данни на проекта бяха изтрити."
+
+msgid ""
+"Project export link has expired. Please generate a new export from your "
+"project settings."
+msgstr ""
+"Връзката към изнесените данни на проекта изгуби давност. Моля, създайте нова "
+"от настройките на проекта."
+
+msgid "Project export started. A download link will be sent by email."
+msgstr ""
+"Изнасянето на проекта започна. Ще получите връзка към данните по е-поща."
+
+msgid "Project home"
+msgstr "Начална страница на проекта"
+
+msgid "ProjectFeature|Disabled"
+msgstr "Изключено"
+
+msgid "ProjectFeature|Everyone with access"
+msgstr "Всеки с достъп"
+
+msgid "ProjectFeature|Only team members"
+msgstr "Само членовете на екипа"
+
+msgid "ProjectFileTree|Name"
+msgstr "Име"
+
+msgid "ProjectLastActivity|Never"
+msgstr "Никога"
+
 msgid "ProjectLifecycle|Stage"
 msgstr "Етап"
 
+msgid "ProjectNetworkGraph|Graph"
+msgstr "Графика"
+
 msgid "Read more"
 msgstr "Прочетете повече"
 
+msgid "Readme"
+msgstr "ПрочетиМе"
+
+msgid "RefSwitcher|Branches"
+msgstr "Клонове"
+
+msgid "RefSwitcher|Tags"
+msgstr "Етикети"
+
 msgid "Related Commits"
 msgstr "Свързани подавания"
 
 msgid "Related Deployed Jobs"
-msgstr "Свързани задачи за внедряване"
+msgstr "Свързани внедрени задачи"
 
 msgid "Related Issues"
 msgstr "Свързани проблеми"
@@ -125,11 +707,87 @@ msgstr "Свързани заявки за сливане"
 msgid "Related Merged Requests"
 msgstr "Свързани приложени заявки за сливане"
 
+msgid "Remind later"
+msgstr "Напомняне по-късно"
+
+msgid "Remove project"
+msgstr "Премахване на проекта"
+
+msgid "Request Access"
+msgstr "Заявка за достъп"
+
+msgid "Revert this commit"
+msgstr "Отмяна на това подаване"
+
+msgid "Revert this merge request"
+msgstr "Отмяна на тази заявка за сливане"
+
+msgid "Save pipeline schedule"
+msgstr "Запазване на плана за схема"
+
+msgid "Schedule a new pipeline"
+msgstr "Създаване на нов план за схема"
+
+msgid "Scheduling Pipelines"
+msgstr "Планиране на схемите"
+
+msgid "Search branches and tags"
+msgstr "Търсете в клоновете и етикетите"
+
+msgid "Select Archive Format"
+msgstr "Изберете формата на архива"
+
+msgid "Select a timezone"
+msgstr "Изберете часова зона"
+
+msgid "Select target branch"
+msgstr "Изберете целеви клон"
+
+msgid "Set a password on your account to pull or push via %{protocol}."
+msgstr ""
+"Задайте парола на профила си, за да можете да изтегляте и изпращате промени "
+"чрез %{protocol}."
+
+msgid "Set up CI"
+msgstr "Настройка на НИ"
+
+msgid "Set up Koding"
+msgstr "Настройка на „Koding“"
+
+msgid "Set up auto deploy"
+msgstr "Настройка на авт. внедряване"
+
+msgid "SetPasswordToCloneLink|set a password"
+msgstr "зададете парола"
+
 msgid "Showing %d event"
 msgid_plural "Showing %d events"
 msgstr[0] "Показване на %d събитие"
 msgstr[1] "Показване на %d събития"
 
+msgid "Source code"
+msgstr "Изходен код"
+
+msgid "StarProject|Star"
+msgstr "Звезда"
+
+msgid "Start a %{new_merge_request} with these changes"
+msgstr "Създайте %{new_merge_request} с тези промени"
+
+msgid "Switch branch/tag"
+msgstr "Преминаване към клон/етикет"
+
+msgid "Tag"
+msgid_plural "Tags"
+msgstr[0] "Етикет"
+msgstr[1] "Етикети"
+
+msgid "Tags"
+msgstr "Етикети"
+
+msgid "Target Branch"
+msgstr "Целеви клон"
+
 msgid ""
 "The coding stage shows the time from the first commit to creating the merge "
 "request. The data will automatically be added here once you create your "
@@ -142,6 +800,9 @@ msgstr ""
 msgid "The collection of events added to the data gathered for that stage."
 msgstr "Съвкупността от събития добавени към данните събрани за този етап."
 
+msgid "The fork relationship has been removed."
+msgstr "Връзката на разклонение беше премахната."
+
 msgid ""
 "The issue stage shows the time it takes from creating an issue to assigning "
 "the issue to a milestone, or add the issue to a list on your Issue Board. "
@@ -155,6 +816,15 @@ msgstr ""
 msgid "The phase of the development lifecycle."
 msgstr "Етапът от цикъла на разработка"
 
+msgid ""
+"The pipelines schedule runs pipelines in the future, repeatedly, for "
+"specific branches or tags. Those scheduled pipelines will inherit limited "
+"project access based on their associated user."
+msgstr ""
+"Планът за схемата ще изпълнява схемите в бъдеще, периодично, за определени "
+"клонове или етикети. Тези планирани схеми ще наследят ограниченията на "
+"достъпа до проекта на свързания с тях потребител."
+
 msgid ""
 "The planning stage shows the time from the previous step to pushing your "
 "first commit. This time will be added automatically once you push your first "
@@ -170,7 +840,18 @@ msgid ""
 "once you have completed the full idea to production cycle."
 msgstr ""
 "Етапът на издаване показва общото време, което е нужно от създаването на "
-"проблем до внедряването на кода в крайната версия."
+"проблем до внедряването на кода в крайната версия. Данните ще бъдат добавени "
+"автоматично след като завършите един пълен цикъл и превърнете първата си "
+"идея в реалност."
+
+msgid "The project can be accessed by any logged in user."
+msgstr "Всеки вписан потребител има достъп до проекта."
+
+msgid "The project can be accessed without any authentication."
+msgstr "Всеки може да има достъп до проекта, без нужда от удостоверяване."
+
+msgid "The repository for this project does not exist."
+msgstr "Хранилището за този проект не съществува."
 
 msgid ""
 "The review stage shows the time from creating the merge request to merging "
@@ -197,8 +878,8 @@ msgid ""
 "first pipeline finishes running."
 msgstr ""
 "Етапът на тестване показва времето, което е нужно на „Gitlab CI“ да изпълни "
-"всички задачи за свързаната заявка за сливане. Данните ще бъдат добавени "
-"автоматично след като приключи изпълнените на първата Ви такава задача."
+"всяка схема от задачи за свързаната заявка за сливане. Данните ще бъдат "
+"добавени автоматично след като приключи изпълнението на първата Ви схема."
 
 msgid "The time taken by each data entry gathered by that stage."
 msgstr "Времето, което отнема всеки запис от данни за съответния етап."
@@ -212,6 +893,13 @@ msgstr ""
 "данни. Например: медианата на 3, 5 и 9 е 5, а медианата на 3, 5, 7 и 8 е "
 "(5+7)/2 = 6."
 
+msgid ""
+"This means you can not push code until you create an empty repository or "
+"import existing one."
+msgstr ""
+"Това означава, че няма да можете да изпращате код, докато не създадете "
+"празно хранилище или не внесете съществуващо такова."
+
 msgid "Time before an issue gets scheduled"
 msgstr "Време преди един проблем да бъде планиран за работа"
 
@@ -225,6 +913,129 @@ msgstr ""
 msgid "Time until first merge request"
 msgstr "Време преди първата заявка за сливане"
 
+msgid "Timeago|%s days ago"
+msgstr "преди %s дни"
+
+msgid "Timeago|%s days remaining"
+msgstr "остават %s дни"
+
+msgid "Timeago|%s hours remaining"
+msgstr "остават %s часа"
+
+msgid "Timeago|%s minutes ago"
+msgstr "преди %s минути"
+
+msgid "Timeago|%s minutes remaining"
+msgstr "остават %s минути"
+
+msgid "Timeago|%s months ago"
+msgstr "преди %s месеца"
+
+msgid "Timeago|%s months remaining"
+msgstr "остават %s месеца"
+
+msgid "Timeago|%s seconds remaining"
+msgstr "остават %s секунди"
+
+msgid "Timeago|%s weeks ago"
+msgstr "преди %s седмици"
+
+msgid "Timeago|%s weeks remaining"
+msgstr "остават %s седмици"
+
+msgid "Timeago|%s years ago"
+msgstr "преди %s години"
+
+msgid "Timeago|%s years remaining"
+msgstr "остават %s години"
+
+msgid "Timeago|1 day remaining"
+msgstr "остава 1 ден"
+
+msgid "Timeago|1 hour remaining"
+msgstr "остава 1 час"
+
+msgid "Timeago|1 minute remaining"
+msgstr "остава 1 минута"
+
+msgid "Timeago|1 month remaining"
+msgstr "остава 1 месец"
+
+msgid "Timeago|1 week remaining"
+msgstr "остава 1 седмица"
+
+msgid "Timeago|1 year remaining"
+msgstr "остава 1 година"
+
+msgid "Timeago|Past due"
+msgstr "Просрочено"
+
+msgid "Timeago|a day ago"
+msgstr "преди един ден"
+
+msgid "Timeago|a month ago"
+msgstr "преди един месец"
+
+msgid "Timeago|a week ago"
+msgstr "преди една седмица"
+
+msgid "Timeago|a while"
+msgstr "преди известно време"
+
+msgid "Timeago|a year ago"
+msgstr "преди една година"
+
+msgid "Timeago|about %s hours ago"
+msgstr "преди около %s часа"
+
+msgid "Timeago|about a minute ago"
+msgstr "преди около една минута"
+
+msgid "Timeago|about an hour ago"
+msgstr "преди около един час"
+
+msgid "Timeago|in %s days"
+msgstr "след %s дни"
+
+msgid "Timeago|in %s hours"
+msgstr "след %s часа"
+
+msgid "Timeago|in %s minutes"
+msgstr "след %s минути"
+
+msgid "Timeago|in %s months"
+msgstr "след %s месеца"
+
+msgid "Timeago|in %s seconds"
+msgstr "след %s секунди"
+
+msgid "Timeago|in %s weeks"
+msgstr "след %s седмици"
+
+msgid "Timeago|in %s years"
+msgstr "след %s години"
+
+msgid "Timeago|in 1 day"
+msgstr "след 1 ден"
+
+msgid "Timeago|in 1 hour"
+msgstr "след 1 час"
+
+msgid "Timeago|in 1 minute"
+msgstr "след 1 минута"
+
+msgid "Timeago|in 1 month"
+msgstr "след 1 месец"
+
+msgid "Timeago|in 1 week"
+msgstr "след 1 седмица"
+
+msgid "Timeago|in 1 year"
+msgstr "след 1 година"
+
+msgid "Timeago|less than a minute ago"
+msgstr "преди по-малко от минута"
+
 msgid "Time|hr"
 msgid_plural "Time|hrs"
 msgstr[0] "час"
@@ -244,17 +1055,125 @@ msgstr "Общо време"
 msgid "Total test time for all commits/merges"
 msgstr "Общо време за тестване на всички подавания/сливания"
 
+msgid "Unstar"
+msgstr "Без звезда"
+
+msgid "Upload New File"
+msgstr "Качване на нов файл"
+
+msgid "Upload file"
+msgstr "Качване на файл"
+
+msgid "UploadLink|click to upload"
+msgstr "щракнете за качване"
+
+msgid "Use your global notification setting"
+msgstr "Използване на глобалната Ви настройка за известията"
+
+msgid "View open merge request"
+msgstr "Преглед на отворената заявка за сливане"
+
+msgid "VisibilityLevel|Internal"
+msgstr "Вътрешен"
+
+msgid "VisibilityLevel|Private"
+msgstr "Частен"
+
+msgid "VisibilityLevel|Public"
+msgstr "Публичен"
+
 msgid "Want to see the data? Please ask an administrator for access."
 msgstr "Искате ли да видите данните? Помолете администратор за достъп."
 
 msgid "We don't have enough data to show this stage."
 msgstr "Няма достатъчно данни за този етап."
 
+msgid "Withdraw Access Request"
+msgstr "Оттегляне на заявката за достъп"
+
+msgid ""
+"You are going to remove %{project_name_with_namespace}.\n"
+"Removed project CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr ""
+"На път сте да премахнете „%{project_name_with_namespace}“.\n"
+"Ако го премахнете, той НЕ може да бъде възстановен!\n"
+"НАИСТИНА ли искате това?"
+
+msgid ""
+"You are going to remove the fork relationship to source project "
+"%{forked_from_project}. Are you ABSOLUTELY sure?"
+msgstr ""
+"На път сте да премахнете връзката на разклонението към оригиналния проект, "
+"„%{forked_from_project}“. НАИСТИНА ли искате това?"
+
+msgid ""
+"You are going to transfer %{project_name_with_namespace} to another owner. "
+"Are you ABSOLUTELY sure?"
+msgstr ""
+"На път сте да прехвърлите „%{project_name_with_namespace}“ към друг "
+"собственик. НАИСТИНА ли искате това?"
+
+msgid "You can only add files when you are on a branch"
+msgstr "Можете да добавяте файлове само когато се намирате в клон"
+
+msgid "You have reached your project limit"
+msgstr "Не можете да създавате повече проекти"
+
+msgid "You must sign in to star a project"
+msgstr "Трябва да се впишете, за да отбележите проект със звезда"
+
 msgid "You need permission."
 msgstr "Нуждаете се от разрешение."
 
+msgid "You will not get any notifications via email"
+msgstr "Няма да получавате никакви известия по е-поща"
+
+msgid "You will only receive notifications for the events you choose"
+msgstr "Ще получавате известия само за събитията, за които желаете"
+
+msgid ""
+"You will only receive notifications for threads you have participated in"
+msgstr "Ще получавате известия само за нещата, в които участвате"
+
+msgid "You will receive notifications for any activity"
+msgstr "Ще получавате известия за всяка дейност"
+
+msgid ""
+"You will receive notifications only for comments in which you were "
+"@mentioned"
+msgstr "Ще получавате известия само за коментари, в които Ви @споменават"
+
+msgid ""
+"You won't be able to pull or push project code via %{protocol} until you "
+"%{set_password_link} on your account"
+msgstr ""
+"Няма да можете да изтегляте или изпращате код в проекта чрез %{protocol}, "
+"докато не %{set_password_link} за профила си"
+
+msgid ""
+"You won't be able to pull or push project code via SSH until you "
+"%{add_ssh_key_link} to your profile"
+msgstr ""
+"Няма да можете да изтегляте или изпращате код в проекта чрез SSH, докато не "
+"%{add_ssh_key_link} в профила си"
+
+msgid "Your name"
+msgstr "Вашето име"
+
 msgid "day"
 msgid_plural "days"
 msgstr[0] "ден"
 msgstr[1] "дни"
 
+msgid "new merge request"
+msgstr "нова заявка за сливане"
+
+msgid "notification emails"
+msgstr "известия по е-поща"
+
+msgid "parent"
+msgid_plural "parents"
+msgstr[0] "родител"
+msgstr[1] "родители"
+
diff --git a/locale/de/gitlab.po b/locale/de/gitlab.po
index 9a660571db9876504f7f5e54df16e125574733ef..ea864091b1090cb1c73b0efbdc819f05a4838cb8 100644
--- a/locale/de/gitlab.po
+++ b/locale/de/gitlab.po
@@ -291,6 +291,9 @@ msgstr "Um diese Daten einsehen zu können, wenden Sie sich bitte an Ihren Admin
 msgid "We don't have enough data to show this stage."
 msgstr "Es liegen nicht genügend Daten vor, um diese Phase anzuzeigen."
 
+msgid "You have reached your project limit"
+msgstr ""
+
 msgid "You need permission."
 msgstr "Sie benötigen Zugriffsrechte."
 
diff --git a/locale/en/gitlab.po b/locale/en/gitlab.po
index 4e44731fc5a381260c7b84c08f01d3f810382e0f..bda3fc09e851c35156cc2573e64ffbd0241b6cd7 100644
--- a/locale/en/gitlab.po
+++ b/locale/en/gitlab.po
@@ -17,207 +17,801 @@ msgstr ""
 "Plural-Forms: nplurals=2; plural=n != 1;\n"
 "\n"
 
+msgid "%d additional commit has been omitted to prevent performance issues."
+msgid_plural "%d additional commits have been omitted to prevent performance issues."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{commit_author_link} committed %{commit_timeago}"
+msgstr ""
+
+msgid "1 pipeline"
+msgid_plural "%d pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "A collection of graphs regarding Continuous Integration"
+msgstr ""
+
+msgid "About auto deploy"
+msgstr ""
+
+msgid "Active"
+msgstr ""
+
+msgid "Activity"
+msgstr ""
+
+msgid "Add Changelog"
+msgstr ""
+
+msgid "Add Contribution guide"
+msgstr ""
+
+msgid "Add License"
+msgstr ""
+
+msgid "Add an SSH key to your profile to pull or push via SSH."
+msgstr ""
+
+msgid "Add new directory"
+msgstr ""
+
+msgid "Archived project! Repository is read-only"
+msgstr ""
+
 msgid "Are you sure you want to delete this pipeline schedule?"
 msgstr ""
 
-msgid "ByAuthor|by"
+msgid "Attach a file by drag &amp; drop or %{upload_link}"
+msgstr ""
+
+msgid "Branch"
+msgid_plural "Branches"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}"
+msgstr ""
+
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr ""
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr ""
+
+msgid "Branches"
+msgstr ""
+
+msgid "Browse Directory"
+msgstr ""
+
+msgid "Browse File"
+msgstr ""
+
+msgid "Browse Files"
+msgstr ""
+
+msgid "Browse files"
+msgstr ""
+
+msgid "ByAuthor|by"
+msgstr ""
+
+msgid "CI configuration"
+msgstr ""
+
+msgid "Cancel"
+msgstr ""
+
+msgid "ChangeTypeActionLabel|Pick into branch"
+msgstr ""
+
+msgid "ChangeTypeActionLabel|Revert in branch"
+msgstr ""
+
+msgid "ChangeTypeAction|Cherry-pick"
+msgstr ""
+
+msgid "ChangeTypeAction|Revert"
+msgstr ""
+
+msgid "Changelog"
+msgstr ""
+
+msgid "Charts"
+msgstr ""
+
+msgid "Cherry-pick this commit"
+msgstr ""
+
+msgid "Cherry-pick this merge request"
+msgstr ""
+
+msgid "CiStatusLabel|canceled"
+msgstr ""
+
+msgid "CiStatusLabel|created"
+msgstr ""
+
+msgid "CiStatusLabel|failed"
+msgstr ""
+
+msgid "CiStatusLabel|manual action"
+msgstr ""
+
+msgid "CiStatusLabel|passed"
+msgstr ""
+
+msgid "CiStatusLabel|passed with warnings"
+msgstr ""
+
+msgid "CiStatusLabel|pending"
+msgstr ""
+
+msgid "CiStatusLabel|skipped"
+msgstr ""
+
+msgid "CiStatusLabel|waiting for manual action"
+msgstr ""
+
+msgid "CiStatusText|blocked"
+msgstr ""
+
+msgid "CiStatusText|canceled"
+msgstr ""
+
+msgid "CiStatusText|created"
+msgstr ""
+
+msgid "CiStatusText|failed"
+msgstr ""
+
+msgid "CiStatusText|manual"
+msgstr ""
+
+msgid "CiStatusText|passed"
+msgstr ""
+
+msgid "CiStatusText|pending"
+msgstr ""
+
+msgid "CiStatusText|skipped"
+msgstr ""
+
+msgid "CiStatus|running"
+msgstr ""
+
+msgid "Commit"
+msgid_plural "Commits"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Commit duration in minutes for last 30 commits"
+msgstr ""
+
+msgid "Commit message"
+msgstr ""
+
+msgid "CommitBoxTitle|Commit"
+msgstr ""
+
+msgid "CommitMessage|Add %{file_name}"
+msgstr ""
+
+msgid "Commits"
+msgstr ""
+
+msgid "Commits feed"
+msgstr ""
+
+msgid "Commits|History"
+msgstr ""
+
+msgid "Committed by"
+msgstr ""
+
+msgid "Compare"
+msgstr ""
+
+msgid "Contribution guide"
+msgstr ""
+
+msgid "Contributors"
+msgstr ""
+
+msgid "Copy URL to clipboard"
+msgstr ""
+
+msgid "Copy commit SHA to clipboard"
+msgstr ""
+
+msgid "Create New Directory"
+msgstr ""
+
+msgid "Create a personal access token on your account to pull or push via %{protocol}."
+msgstr ""
+
+msgid "Create directory"
+msgstr ""
+
+msgid "Create empty bare repository"
+msgstr ""
+
+msgid "Create merge request"
+msgstr ""
+
+msgid "Create new..."
+msgstr ""
+
+msgid "CreateNewFork|Fork"
+msgstr ""
+
+msgid "CreateTag|Tag"
+msgstr ""
+
+msgid "CreateTokenToCloneLink|create a personal access token"
+msgstr ""
+
+msgid "Cron Timezone"
+msgstr ""
+
+msgid "Cron syntax"
+msgstr ""
+
+msgid "Custom notification events"
+msgstr ""
+
+msgid "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 %{notification_link}."
+msgstr ""
+
+msgid "Cycle Analytics"
+msgstr ""
+
+msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project."
+msgstr ""
+
+msgid "CycleAnalyticsStage|Code"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Issue"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Plan"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Production"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Review"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Staging"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Test"
+msgstr ""
+
+msgid "Define a custom pattern with cron syntax"
+msgstr ""
+
+msgid "Delete"
+msgstr ""
+
+msgid "Deploy"
+msgid_plural "Deploys"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Description"
+msgstr ""
+
+msgid "Directory name"
+msgstr ""
+
+msgid "Don't show again"
+msgstr ""
+
+msgid "Download"
+msgstr ""
+
+msgid "Download tar"
+msgstr ""
+
+msgid "Download tar.bz2"
+msgstr ""
+
+msgid "Download tar.gz"
+msgstr ""
+
+msgid "Download zip"
+msgstr ""
+
+msgid "DownloadArtifacts|Download"
+msgstr ""
+
+msgid "DownloadCommit|Email Patches"
+msgstr ""
+
+msgid "DownloadCommit|Plain Diff"
+msgstr ""
+
+msgid "DownloadSource|Download"
+msgstr ""
+
+msgid "Edit"
+msgstr ""
+
+msgid "Edit Pipeline Schedule %{id}"
+msgstr ""
+
+msgid "Every day (at 4:00am)"
+msgstr ""
+
+msgid "Every month (on the 1st at 4:00am)"
+msgstr ""
+
+msgid "Every week (Sundays at 4:00am)"
+msgstr ""
+
+msgid "Failed to change the owner"
+msgstr ""
+
+msgid "Failed to remove the pipeline schedule"
+msgstr ""
+
+msgid "Files"
+msgstr ""
+
+msgid "Filter by commit message"
+msgstr ""
+
+msgid "Find by path"
+msgstr ""
+
+msgid "Find file"
+msgstr ""
+
+msgid "FirstPushedBy|First"
+msgstr ""
+
+msgid "FirstPushedBy|pushed by"
+msgstr ""
+
+msgid "Fork"
+msgid_plural "Forks"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ForkedFromProjectPath|Forked from"
+msgstr ""
+
+msgid "From issue creation until deploy to production"
+msgstr ""
+
+msgid "From merge request merge until deploy to production"
+msgstr ""
+
+msgid "Go to your fork"
+msgstr ""
+
+msgid "GoToYourFork|Fork"
+msgstr ""
+
+msgid "Home"
+msgstr ""
+
+msgid "Housekeeping successfully started"
+msgstr ""
+
+msgid "Import repository"
+msgstr ""
+
+msgid "Interval Pattern"
+msgstr ""
+
+msgid "Introducing Cycle Analytics"
+msgstr ""
+
+msgid "Jobs for last month"
+msgstr ""
+
+msgid "Jobs for last week"
+msgstr ""
+
+msgid "Jobs for last year"
+msgstr ""
+
+msgid "LFSStatus|Disabled"
+msgstr ""
+
+msgid "LFSStatus|Enabled"
+msgstr ""
+
+msgid "Last %d day"
+msgid_plural "Last %d days"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Last Pipeline"
+msgstr ""
+
+msgid "Last Update"
+msgstr ""
+
+msgid "Last commit"
+msgstr ""
+
+msgid "Learn more in the"
+msgstr ""
+
+msgid "Learn more in the|pipeline schedules documentation"
+msgstr ""
+
+msgid "Leave group"
+msgstr ""
+
+msgid "Leave project"
+msgstr ""
+
+msgid "Limited to showing %d event at most"
+msgid_plural "Limited to showing %d events at most"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Median"
+msgstr ""
+
+msgid "MissingSSHKeyWarningLink|add an SSH key"
+msgstr ""
+
+msgid "New Issue"
+msgid_plural "New Issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "New Pipeline Schedule"
+msgstr ""
+
+msgid "New branch"
+msgstr ""
+
+msgid "New directory"
+msgstr ""
+
+msgid "New file"
+msgstr ""
+
+msgid "New issue"
+msgstr ""
+
+msgid "New merge request"
+msgstr ""
+
+msgid "New schedule"
+msgstr ""
+
+msgid "New snippet"
+msgstr ""
+
+msgid "New tag"
+msgstr ""
+
+msgid "No repository"
+msgstr ""
+
+msgid "No schedules"
+msgstr ""
+
+msgid "Not available"
+msgstr ""
+
+msgid "Not enough data"
+msgstr ""
+
+msgid "Notification events"
+msgstr ""
+
+msgid "NotificationEvent|Close issue"
+msgstr ""
+
+msgid "NotificationEvent|Close merge request"
+msgstr ""
+
+msgid "NotificationEvent|Failed pipeline"
+msgstr ""
+
+msgid "NotificationEvent|Merge merge request"
+msgstr ""
+
+msgid "NotificationEvent|New issue"
+msgstr ""
+
+msgid "NotificationEvent|New merge request"
+msgstr ""
+
+msgid "NotificationEvent|New note"
+msgstr ""
+
+msgid "NotificationEvent|Reassign issue"
+msgstr ""
+
+msgid "NotificationEvent|Reassign merge request"
+msgstr ""
+
+msgid "NotificationEvent|Reopen issue"
+msgstr ""
+
+msgid "NotificationEvent|Successful pipeline"
+msgstr ""
+
+msgid "NotificationLevel|Custom"
+msgstr ""
+
+msgid "NotificationLevel|Disabled"
+msgstr ""
+
+msgid "NotificationLevel|Global"
+msgstr ""
+
+msgid "NotificationLevel|On mention"
+msgstr ""
+
+msgid "NotificationLevel|Participate"
+msgstr ""
+
+msgid "NotificationLevel|Watch"
+msgstr ""
+
+msgid "OfSearchInADropdown|Filter"
+msgstr ""
+
+msgid "OpenedNDaysAgo|Opened"
+msgstr ""
+
+msgid "Options"
+msgstr ""
+
+msgid "Owner"
+msgstr ""
+
+msgid "Pipeline"
+msgstr ""
+
+msgid "Pipeline Health"
+msgstr ""
+
+msgid "Pipeline Schedule"
+msgstr ""
+
+msgid "Pipeline Schedules"
+msgstr ""
+
+msgid "PipelineCharts|Failed:"
+msgstr ""
+
+msgid "PipelineCharts|Overall statistics"
+msgstr ""
+
+msgid "PipelineCharts|Success ratio:"
+msgstr ""
+
+msgid "PipelineCharts|Successful:"
+msgstr ""
+
+msgid "PipelineCharts|Total:"
+msgstr ""
+
+msgid "PipelineSchedules|Activated"
+msgstr ""
+
+msgid "PipelineSchedules|Active"
+msgstr ""
+
+msgid "PipelineSchedules|All"
+msgstr ""
+
+msgid "PipelineSchedules|Inactive"
+msgstr ""
+
+msgid "PipelineSchedules|Next Run"
 msgstr ""
 
-msgid "Cancel"
+msgid "PipelineSchedules|None"
 msgstr ""
 
-msgid "Commit"
-msgid_plural "Commits"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "Cron Timezone"
+msgid "PipelineSchedules|Provide a short description for this pipeline"
 msgstr ""
 
-msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project."
+msgid "PipelineSchedules|Take ownership"
 msgstr ""
 
-msgid "CycleAnalyticsStage|Code"
+msgid "PipelineSchedules|Target"
 msgstr ""
 
-msgid "CycleAnalyticsStage|Issue"
+msgid "PipelineSheduleIntervalPattern|Custom"
 msgstr ""
 
-msgid "CycleAnalyticsStage|Plan"
+msgid "Pipelines"
 msgstr ""
 
-msgid "CycleAnalyticsStage|Production"
+msgid "Pipelines charts"
 msgstr ""
 
-msgid "CycleAnalyticsStage|Review"
+msgid "Pipeline|all"
 msgstr ""
 
-msgid "CycleAnalyticsStage|Staging"
+msgid "Pipeline|success"
 msgstr ""
 
-msgid "CycleAnalyticsStage|Test"
+msgid "Pipeline|with stage"
 msgstr ""
 
-msgid "Delete"
+msgid "Pipeline|with stages"
 msgstr ""
 
-msgid "Deploy"
-msgid_plural "Deploys"
-msgstr[0] ""
-msgstr[1] ""
+msgid "Project '%{project_name}' queued for deletion."
+msgstr ""
 
-msgid "Description"
+msgid "Project '%{project_name}' was successfully created."
 msgstr ""
 
-msgid "Edit"
+msgid "Project '%{project_name}' was successfully updated."
 msgstr ""
 
-msgid "Edit Pipeline Schedule %{id}"
+msgid "Project '%{project_name}' will be deleted."
 msgstr ""
 
-msgid "Failed to change the owner"
+msgid "Project access must be granted explicitly to each user."
 msgstr ""
 
-msgid "Failed to remove the pipeline schedule"
+msgid "Project export could not be deleted."
 msgstr ""
 
-msgid "Filter"
+msgid "Project export has been deleted."
 msgstr ""
 
-msgid "FirstPushedBy|First"
+msgid "Project export link has expired. Please generate a new export from your project settings."
 msgstr ""
 
-msgid "FirstPushedBy|pushed by"
+msgid "Project export started. A download link will be sent by email."
 msgstr ""
 
-msgid "From issue creation until deploy to production"
+msgid "Project home"
 msgstr ""
 
-msgid "From merge request merge until deploy to production"
+msgid "ProjectFeature|Disabled"
 msgstr ""
 
-msgid "Interval Pattern"
+msgid "ProjectFeature|Everyone with access"
 msgstr ""
 
-msgid "Introducing Cycle Analytics"
+msgid "ProjectFeature|Only team members"
 msgstr ""
 
-msgid "Last %d day"
-msgid_plural "Last %d days"
-msgstr[0] ""
-msgstr[1] ""
+msgid "ProjectFileTree|Name"
+msgstr ""
 
-msgid "Last Pipeline"
+msgid "ProjectLastActivity|Never"
 msgstr ""
 
-msgid "Limited to showing %d event at most"
-msgid_plural "Limited to showing %d events at most"
-msgstr[0] ""
-msgstr[1] ""
+msgid "ProjectLifecycle|Stage"
+msgstr ""
 
-msgid "Median"
+msgid "ProjectNetworkGraph|Graph"
 msgstr ""
 
-msgid "New Issue"
-msgid_plural "New Issues"
-msgstr[0] ""
-msgstr[1] ""
+msgid "Read more"
+msgstr ""
 
-msgid "New Pipeline Schedule"
+msgid "Readme"
 msgstr ""
 
-msgid "No schedules"
+msgid "RefSwitcher|Branches"
 msgstr ""
 
-msgid "Not available"
+msgid "RefSwitcher|Tags"
 msgstr ""
 
-msgid "Not enough data"
+msgid "Related Commits"
 msgstr ""
 
-msgid "OpenedNDaysAgo|Opened"
+msgid "Related Deployed Jobs"
 msgstr ""
 
-msgid "Owner"
+msgid "Related Issues"
 msgstr ""
 
-msgid "Pipeline Health"
+msgid "Related Jobs"
 msgstr ""
 
-msgid "Pipeline Schedule"
+msgid "Related Merge Requests"
 msgstr ""
 
-msgid "Pipeline Schedules"
+msgid "Related Merged Requests"
 msgstr ""
 
-msgid "PipelineSchedules|Activated"
+msgid "Remind later"
 msgstr ""
 
-msgid "PipelineSchedules|Active"
+msgid "Remove project"
 msgstr ""
 
-msgid "PipelineSchedules|All"
+msgid "Request Access"
 msgstr ""
 
-msgid "PipelineSchedules|Inactive"
+msgid "Revert this commit"
 msgstr ""
 
-msgid "PipelineSchedules|Next Run"
+msgid "Revert this merge request"
 msgstr ""
 
-msgid "PipelineSchedules|None"
+msgid "Save pipeline schedule"
 msgstr ""
 
-msgid "PipelineSchedules|Provide a short description for this pipeline"
+msgid "Schedule a new pipeline"
 msgstr ""
 
-msgid "PipelineSchedules|Take ownership"
+msgid "Scheduling Pipelines"
 msgstr ""
 
-msgid "PipelineSchedules|Target"
+msgid "Search branches and tags"
 msgstr ""
 
-msgid "ProjectLifecycle|Stage"
+msgid "Select Archive Format"
 msgstr ""
 
-msgid "Read more"
+msgid "Select a timezone"
 msgstr ""
 
-msgid "Related Commits"
+msgid "Select target branch"
 msgstr ""
 
-msgid "Related Deployed Jobs"
+msgid "Set a password on your account to pull or push via %{protocol}."
 msgstr ""
 
-msgid "Related Issues"
+msgid "Set up CI"
 msgstr ""
 
-msgid "Related Jobs"
+msgid "Set up Koding"
 msgstr ""
 
-msgid "Related Merge Requests"
+msgid "Set up auto deploy"
 msgstr ""
 
-msgid "Related Merged Requests"
+msgid "SetPasswordToCloneLink|set a password"
 msgstr ""
 
-msgid "Save pipeline schedule"
+msgid "Showing %d event"
+msgid_plural "Showing %d events"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Source code"
 msgstr ""
 
-msgid "Schedule a new pipeline"
+msgid "StarProject|Star"
 msgstr ""
 
-msgid "Select a timezone"
+msgid "Start a %{new_merge_request} with these changes"
 msgstr ""
 
-msgid "Select target branch"
+msgid "Switch branch/tag"
 msgstr ""
 
-msgid "Showing %d event"
-msgid_plural "Showing %d events"
+msgid "Tag"
+msgid_plural "Tags"
 msgstr[0] ""
 msgstr[1] ""
 
+msgid "Tags"
+msgstr ""
+
 msgid "Target Branch"
 msgstr ""
 
@@ -227,18 +821,33 @@ msgstr ""
 msgid "The collection of events added to the data gathered for that stage."
 msgstr ""
 
+msgid "The fork relationship has been removed."
+msgstr ""
+
 msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage."
 msgstr ""
 
 msgid "The phase of the development lifecycle."
 msgstr ""
 
+msgid "The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user."
+msgstr ""
+
 msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit."
 msgstr ""
 
 msgid "The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
 msgstr ""
 
+msgid "The project can be accessed by any logged in user."
+msgstr ""
+
+msgid "The project can be accessed without any authentication."
+msgstr ""
+
+msgid "The repository for this project does not exist."
+msgstr ""
+
 msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request."
 msgstr ""
 
@@ -254,6 +863,9 @@ msgstr ""
 msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
 msgstr ""
 
+msgid "This means you can not push code until you create an empty repository or import existing one."
+msgstr ""
+
 msgid "Time before an issue gets scheduled"
 msgstr ""
 
@@ -266,6 +878,129 @@ msgstr ""
 msgid "Time until first merge request"
 msgstr ""
 
+msgid "Timeago|%s days ago"
+msgstr ""
+
+msgid "Timeago|%s days remaining"
+msgstr ""
+
+msgid "Timeago|%s hours remaining"
+msgstr ""
+
+msgid "Timeago|%s minutes ago"
+msgstr ""
+
+msgid "Timeago|%s minutes remaining"
+msgstr ""
+
+msgid "Timeago|%s months ago"
+msgstr ""
+
+msgid "Timeago|%s months remaining"
+msgstr ""
+
+msgid "Timeago|%s seconds remaining"
+msgstr ""
+
+msgid "Timeago|%s weeks ago"
+msgstr ""
+
+msgid "Timeago|%s weeks remaining"
+msgstr ""
+
+msgid "Timeago|%s years ago"
+msgstr ""
+
+msgid "Timeago|%s years remaining"
+msgstr ""
+
+msgid "Timeago|1 day remaining"
+msgstr ""
+
+msgid "Timeago|1 hour remaining"
+msgstr ""
+
+msgid "Timeago|1 minute remaining"
+msgstr ""
+
+msgid "Timeago|1 month remaining"
+msgstr ""
+
+msgid "Timeago|1 week remaining"
+msgstr ""
+
+msgid "Timeago|1 year remaining"
+msgstr ""
+
+msgid "Timeago|Past due"
+msgstr ""
+
+msgid "Timeago|a day ago"
+msgstr ""
+
+msgid "Timeago|a month ago"
+msgstr ""
+
+msgid "Timeago|a week ago"
+msgstr ""
+
+msgid "Timeago|a while"
+msgstr ""
+
+msgid "Timeago|a year ago"
+msgstr ""
+
+msgid "Timeago|about %s hours ago"
+msgstr ""
+
+msgid "Timeago|about a minute ago"
+msgstr ""
+
+msgid "Timeago|about an hour ago"
+msgstr ""
+
+msgid "Timeago|in %s days"
+msgstr ""
+
+msgid "Timeago|in %s hours"
+msgstr ""
+
+msgid "Timeago|in %s minutes"
+msgstr ""
+
+msgid "Timeago|in %s months"
+msgstr ""
+
+msgid "Timeago|in %s seconds"
+msgstr ""
+
+msgid "Timeago|in %s weeks"
+msgstr ""
+
+msgid "Timeago|in %s years"
+msgstr ""
+
+msgid "Timeago|in 1 day"
+msgstr ""
+
+msgid "Timeago|in 1 hour"
+msgstr ""
+
+msgid "Timeago|in 1 minute"
+msgstr ""
+
+msgid "Timeago|in 1 month"
+msgstr ""
+
+msgid "Timeago|in 1 week"
+msgstr ""
+
+msgid "Timeago|in 1 year"
+msgstr ""
+
+msgid "Timeago|less than a minute ago"
+msgstr ""
+
 msgid "Time|hr"
 msgid_plural "Time|hrs"
 msgstr[0] ""
@@ -285,16 +1020,102 @@ msgstr ""
 msgid "Total test time for all commits/merges"
 msgstr ""
 
+msgid "Unstar"
+msgstr ""
+
+msgid "Upload New File"
+msgstr ""
+
+msgid "Upload file"
+msgstr ""
+
+msgid "UploadLink|click to upload"
+msgstr ""
+
+msgid "Use your global notification setting"
+msgstr ""
+
+msgid "View open merge request"
+msgstr ""
+
+msgid "VisibilityLevel|Internal"
+msgstr ""
+
+msgid "VisibilityLevel|Private"
+msgstr ""
+
+msgid "VisibilityLevel|Public"
+msgstr ""
+
 msgid "Want to see the data? Please ask an administrator for access."
 msgstr ""
 
 msgid "We don't have enough data to show this stage."
 msgstr ""
 
+msgid "Withdraw Access Request"
+msgstr ""
+
+msgid ""
+"You are going to remove %{project_name_with_namespace}.\n"
+"Removed project CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You can only add files when you are on a branch"
+msgstr ""
+
+msgid "You have reached your project limit"
+msgstr ""
+
+msgid "You must sign in to star a project"
+msgstr ""
+
 msgid "You need permission."
 msgstr ""
 
+msgid "You will not get any notifications via email"
+msgstr ""
+
+msgid "You will only receive notifications for the events you choose"
+msgstr ""
+
+msgid "You will only receive notifications for threads you have participated in"
+msgstr ""
+
+msgid "You will receive notifications for any activity"
+msgstr ""
+
+msgid "You will receive notifications only for comments in which you were @mentioned"
+msgstr ""
+
+msgid "You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account"
+msgstr ""
+
+msgid "You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile"
+msgstr ""
+
+msgid "Your name"
+msgstr ""
+
 msgid "day"
 msgid_plural "days"
 msgstr[0] ""
 msgstr[1] ""
+
+msgid "new merge request"
+msgstr ""
+
+msgid "notification emails"
+msgstr ""
+
+msgid "parent"
+msgid_plural "parents"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/locale/eo/gitlab.po b/locale/eo/gitlab.po
new file mode 100644
index 0000000000000000000000000000000000000000..0ca8dfca2664c7b75aed6290ac9befc937864f5d
--- /dev/null
+++ b/locale/eo/gitlab.po
@@ -0,0 +1,1181 @@
+# Huang Tao <htve@outlook.com>, 2017. #zanata
+# Lyubomir Vasilev <lyubomirv@abv.bg>, 2017. #zanata
+msgid ""
+msgstr ""
+"Project-Id-Version: gitlab 1.0.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-06-19 15:50-0500\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"PO-Revision-Date: 2017-07-05 02:56-0400\n"
+"Last-Translator: Lyubomir Vasilev <lyubomirv@abv.bg>\n"
+"Language-Team: Esperanto (https://translate.zanata.org/project/view/GitLab)\n"
+"Language: eo\n"
+"X-Generator: Zanata 3.9.6\n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+
+msgid "%d additional commit has been omitted to prevent performance issues."
+msgid_plural ""
+"%d additional commits have been omitted to prevent performance issues."
+msgstr[0] "%d enmetado estis transsaltita, por ne troŝarĝi la sistemon."
+msgstr[1] "%d enmetadoj estis transsaltitaj, por ne troŝarĝi la sistemon."
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] "%d enmetado"
+msgstr[1] "%d enmetadoj"
+
+msgid "%{commit_author_link} committed %{commit_timeago}"
+msgstr "%{commit_author_link} enmetis %{commit_timeago}"
+
+msgid "About auto deploy"
+msgstr "Pri la aŭtomata disponigado"
+
+msgid "Active"
+msgstr "Aktiva"
+
+msgid "Activity"
+msgstr "Aktiveco"
+
+msgid "Add Changelog"
+msgstr "Aldoni liston de ŝanĝoj"
+
+msgid "Add Contribution guide"
+msgstr "Aldoni gvidliniojn por kontribuado"
+
+msgid "Add License"
+msgstr "Aldoni rajtigilon"
+
+msgid "Add an SSH key to your profile to pull or push via SSH."
+msgstr ""
+"Aldonu SSH-ŝlosilon al via profilo por ebligi al vi eltiri kaj alpuŝi per "
+"SSH."
+
+msgid "Add new directory"
+msgstr "Aldoni novan dosierujon"
+
+msgid "Archived project! Repository is read-only"
+msgstr "Arkivita projekto! La deponejo permesas nur legadon"
+
+msgid "Are you sure you want to delete this pipeline schedule?"
+msgstr "Ĉu vi certe volas forigi ĉi tiun ĉenstablan planon?"
+
+msgid "Attach a file by drag &amp; drop or %{upload_link}"
+msgstr "Alkroĉu dosieron per ŝovmetado aŭ %{upload_link}"
+
+msgid "Branch"
+msgid_plural "Branches"
+msgstr[0] "Branĉo"
+msgstr[1] "Branĉoj"
+
+msgid ""
+"Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, "
+"choose a GitLab CI Yaml template and commit your changes. "
+"%{link_to_autodeploy_doc}"
+msgstr ""
+"La branĉo <strong>%{branch_name}</strong> estis kreita. Por agordi aŭtomatan "
+"disponigadon, bonvolu elekti Yaml-ŝablonon por GitLab CI kaj enmeti viajn "
+"ŝanĝojn. %{link_to_autodeploy_doc}"
+
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr "Serĉu branĉon"
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr "Iri al branĉo"
+
+msgid "Branches"
+msgstr "Branĉoj"
+
+msgid "Browse Directory"
+msgstr "Foliumi dosierujon"
+
+msgid "Browse File"
+msgstr "Foliumi dosieron"
+
+msgid "Browse Files"
+msgstr "Foliumi dosierojn"
+
+msgid "Browse files"
+msgstr "Elekti dosierojn"
+
+msgid "ByAuthor|by"
+msgstr "de"
+
+msgid "CI configuration"
+msgstr "Agordoj de seninterrompa integrado"
+
+msgid "Cancel"
+msgstr "Nuligi"
+
+msgid "ChangeTypeActionLabel|Pick into branch"
+msgstr "Elekti en branĉon"
+
+msgid "ChangeTypeActionLabel|Revert in branch"
+msgstr "Malfari en branĉo"
+
+msgid "ChangeTypeAction|Cherry-pick"
+msgstr "Precize elekti"
+
+msgid "ChangeTypeAction|Revert"
+msgstr "Malfari"
+
+msgid "Changelog"
+msgstr "Listo de ŝanĝoj"
+
+msgid "Charts"
+msgstr "Diagramoj"
+
+msgid "Cherry-pick this commit"
+msgstr "Precize elekti ĉi tiun kunmetadon"
+
+msgid "Cherry-pick this merge request"
+msgstr "Precize elekti ĉi tiun peton pri kunfando"
+
+msgid "CiStatusLabel|canceled"
+msgstr "nuligita"
+
+msgid "CiStatusLabel|created"
+msgstr "kreita"
+
+msgid "CiStatusLabel|failed"
+msgstr "malsukcesa"
+
+msgid "CiStatusLabel|manual action"
+msgstr "mana ago"
+
+msgid "CiStatusLabel|passed"
+msgstr "sukcesa"
+
+msgid "CiStatusLabel|passed with warnings"
+msgstr "sukcesa, kun avertoj"
+
+msgid "CiStatusLabel|pending"
+msgstr "okazonta"
+
+msgid "CiStatusLabel|skipped"
+msgstr "transsaltita"
+
+msgid "CiStatusLabel|waiting for manual action"
+msgstr "atendanta manan agon"
+
+msgid "CiStatusText|blocked"
+msgstr "blokita"
+
+msgid "CiStatusText|canceled"
+msgstr "nuligita"
+
+msgid "CiStatusText|created"
+msgstr "kreita"
+
+msgid "CiStatusText|failed"
+msgstr "malsukcesa"
+
+msgid "CiStatusText|manual"
+msgstr "mana"
+
+msgid "CiStatusText|passed"
+msgstr "sukcesa"
+
+msgid "CiStatusText|pending"
+msgstr "okazonta"
+
+msgid "CiStatusText|skipped"
+msgstr "transsaltita"
+
+msgid "CiStatus|running"
+msgstr "plenumiĝanta"
+
+msgid "Commit"
+msgid_plural "Commits"
+msgstr[0] "Enmetado"
+msgstr[1] "Enmetadoj"
+
+msgid "Commit message"
+msgstr "Mesaĝo pri la enmetado"
+
+msgid "CommitBoxTitle|Commit"
+msgstr "Enmeti"
+
+msgid "CommitMessage|Add %{file_name}"
+msgstr "Aldoni „%{file_name}“"
+
+msgid "Commits"
+msgstr "Enmetadoj"
+
+msgid "Commits feed"
+msgstr "Fluo de enmetadoj"
+
+msgid "Commits|History"
+msgstr "Historio"
+
+msgid "Committed by"
+msgstr "Enmetita de"
+
+msgid "Compare"
+msgstr "Kompari"
+
+msgid "Contribution guide"
+msgstr "Gvidlinioj por kontribuado"
+
+msgid "Contributors"
+msgstr "Kontribuantoj"
+
+msgid "Copy URL to clipboard"
+msgstr "Kopii la adreson en la kopibufron"
+
+msgid "Copy commit SHA to clipboard"
+msgstr "Kopii la identigilon de la enmetado"
+
+msgid "Create New Directory"
+msgstr "Krei novan dosierujon"
+
+msgid "Create directory"
+msgstr "Krei dosierujon"
+
+msgid "Create empty bare repository"
+msgstr "Krei malplenan deponejon"
+
+msgid "Create merge request"
+msgstr "Krei peton pri kunfando"
+
+msgid "Create new..."
+msgstr "Krei novan…"
+
+msgid "CreateNewFork|Fork"
+msgstr "Disbranĉigi"
+
+msgid "CreateTag|Tag"
+msgstr "Etikedo"
+
+msgid "Cron Timezone"
+msgstr "Horzono por Cron"
+
+msgid "Cron syntax"
+msgstr "La sintakso de Cron"
+
+msgid "Custom notification events"
+msgstr "Propraj sciigaj eventoj"
+
+msgid ""
+"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 %{notification_link}."
+msgstr ""
+"La propraj sciigaj niveloj estas la samaj kiel la niveloj de partoprenado. "
+"Uzante la proprajn sciigajn nivelojn, vi ricevos ankaŭ sciigojn por "
+"elektitaj de vi eventoj. Por lerni pli, bonvolu vidi %{notification_link}."
+
+msgid "Cycle Analytics"
+msgstr "Cikla analizo"
+
+msgid ""
+"Cycle Analytics gives an overview of how much time it takes to go from idea "
+"to production in your project."
+msgstr ""
+"La cikla analizo esploras kiom da tempo necesas por disvolvi ideon ĝis ĝi "
+"fariĝos realaĵo."
+
+msgid "CycleAnalyticsStage|Code"
+msgstr "Programado"
+
+msgid "CycleAnalyticsStage|Issue"
+msgstr "Problemo"
+
+msgid "CycleAnalyticsStage|Plan"
+msgstr "Plano"
+
+msgid "CycleAnalyticsStage|Production"
+msgstr "Eldonado"
+
+msgid "CycleAnalyticsStage|Review"
+msgstr "Kontrolo"
+
+msgid "CycleAnalyticsStage|Staging"
+msgstr "Preparo por eldono"
+
+msgid "CycleAnalyticsStage|Test"
+msgstr "Testado"
+
+msgid "Define a custom pattern with cron syntax"
+msgstr "Difini propran ŝablonon, uzante la sintakson de Cron"
+
+msgid "Delete"
+msgstr "Forigi"
+
+msgid "Deploy"
+msgid_plural "Deploys"
+msgstr[0] "Disponigado"
+msgstr[1] "Disponigadoj"
+
+msgid "Description"
+msgstr "Priskribo"
+
+msgid "Directory name"
+msgstr "Nomo de dosierujo"
+
+msgid "Don't show again"
+msgstr "Ne montru denove"
+
+msgid "Download"
+msgstr "Elŝuti"
+
+msgid "Download tar"
+msgstr "Elŝuti en formato „tar“"
+
+msgid "Download tar.bz2"
+msgstr "Elŝuti en formato „tar.bz2“"
+
+msgid "Download tar.gz"
+msgstr "Elŝuti en formato „tar.gz“"
+
+msgid "Download zip"
+msgstr "Elŝuti en formato „zip“"
+
+msgid "DownloadArtifacts|Download"
+msgstr "Elŝuti"
+
+msgid "DownloadCommit|Email Patches"
+msgstr "Sendi flikaĵojn per retpoŝto"
+
+msgid "DownloadCommit|Plain Diff"
+msgstr "Normala dosiero kun diferencoj"
+
+msgid "DownloadSource|Download"
+msgstr "Elŝuti"
+
+msgid "Edit"
+msgstr "Redakti"
+
+msgid "Edit Pipeline Schedule %{id}"
+msgstr "Redakti ĉenstablan planon %{id}"
+
+msgid "Every day (at 4:00am)"
+msgstr "Ĉiutage (je 4:00)"
+
+msgid "Every month (on the 1st at 4:00am)"
+msgstr "Ĉiumonate (en la 1a de la monato, je 4:00)"
+
+msgid "Every week (Sundays at 4:00am)"
+msgstr "Ĉiusemajne (en dimanĉo, je 4:00)"
+
+msgid "Failed to change the owner"
+msgstr "Ne eblas ŝanĝi la posedanton"
+
+msgid "Failed to remove the pipeline schedule"
+msgstr "Ne eblas forigi la ĉenstablan planon"
+
+msgid "Files"
+msgstr "Dosieroj"
+
+msgid "Filter by commit message"
+msgstr "Filtri per mesaĝo"
+
+msgid "Find by path"
+msgstr "Trovi per dosierindiko"
+
+msgid "Find file"
+msgstr "Trovi dosieron"
+
+msgid "FirstPushedBy|First"
+msgstr "Unue"
+
+msgid "FirstPushedBy|pushed by"
+msgstr "alpuŝita de"
+
+msgid "Fork"
+msgid_plural "Forks"
+msgstr[0] "Disbranĉigo"
+msgstr[1] "Disbranĉigoj"
+
+msgid "ForkedFromProjectPath|Forked from"
+msgstr "Disbranĉigita el"
+
+msgid "From issue creation until deploy to production"
+msgstr "De la kreado de la problemo ĝis la disponigado en la publika versio"
+
+msgid "From merge request merge until deploy to production"
+msgstr ""
+"De la kunfandado de la peto pri kunfando ĝis la disponigado en la publika "
+"versio"
+
+msgid "Go to your fork"
+msgstr "Al via disbranĉigo"
+
+msgid "GoToYourFork|Fork"
+msgstr "Disbranĉigo"
+
+msgid "Home"
+msgstr "Hejmo"
+
+msgid "Housekeeping successfully started"
+msgstr "La refreŝigo komenciĝis sukcese"
+
+msgid "Import repository"
+msgstr "Enporti deponejon"
+
+msgid "Interval Pattern"
+msgstr "Intervala ŝablono"
+
+msgid "Introducing Cycle Analytics"
+msgstr "Ni prezentas al vi la ciklan analizon"
+
+msgid "LFSStatus|Disabled"
+msgstr "Malŝaltita"
+
+msgid "LFSStatus|Enabled"
+msgstr "Ŝaltita"
+
+msgid "Last %d day"
+msgid_plural "Last %d days"
+msgstr[0] "La lasta %d tago"
+msgstr[1] "La lastaj %d tagoj"
+
+msgid "Last Pipeline"
+msgstr "Lasta ĉenstablo"
+
+msgid "Last Update"
+msgstr "Lasta ĝisdatigo"
+
+msgid "Last commit"
+msgstr "Lasta enmetado"
+
+msgid "Learn more in the"
+msgstr "Lernu pli en la"
+
+msgid "Learn more in the|pipeline schedules documentation"
+msgstr "dokumentado pri ĉenstablaj planoj"
+
+msgid "Leave group"
+msgstr "Forlasi la grupon"
+
+msgid "Leave project"
+msgstr "Forlasi la projekton"
+
+msgid "Limited to showing %d event at most"
+msgid_plural "Limited to showing %d events at most"
+msgstr[0] "Limigita al montrado de ne pli ol %d evento"
+msgstr[1] "Limigita al montrado de ne pli ol %d eventoj"
+
+msgid "Median"
+msgstr "Mediano"
+
+msgid "MissingSSHKeyWarningLink|add an SSH key"
+msgstr "aldonos SSH-ŝlosilon"
+
+msgid "New Issue"
+msgid_plural "New Issues"
+msgstr[0] "Nova problemo"
+msgstr[1] "Novaj problemoj"
+
+msgid "New Pipeline Schedule"
+msgstr "Nova ĉenstabla plano"
+
+msgid "New branch"
+msgstr "Nova branĉo"
+
+msgid "New directory"
+msgstr "Nova dosierujo"
+
+msgid "New file"
+msgstr "Nova dosiero"
+
+msgid "New issue"
+msgstr "Nova problemo"
+
+msgid "New merge request"
+msgstr "Nova peto pri kunfando"
+
+msgid "New schedule"
+msgstr "Nova plano"
+
+msgid "New snippet"
+msgstr "Nova kodaĵo"
+
+msgid "New tag"
+msgstr "Nova etikedo"
+
+msgid "No repository"
+msgstr "Ne estas deponejo"
+
+msgid "No schedules"
+msgstr "Ne estas planoj"
+
+msgid "Not available"
+msgstr "Ne disponebla"
+
+msgid "Not enough data"
+msgstr "Ne estas sufiĉe da datenoj"
+
+msgid "Notification events"
+msgstr "Sciigaj eventoj"
+
+msgid "NotificationEvent|Close issue"
+msgstr "Fermi problemon"
+
+msgid "NotificationEvent|Close merge request"
+msgstr "Fermi peton pri kunfando"
+
+msgid "NotificationEvent|Failed pipeline"
+msgstr "Malsukcesa ĉenstablo"
+
+msgid "NotificationEvent|Merge merge request"
+msgstr "Apliki peton pri kunfando"
+
+msgid "NotificationEvent|New issue"
+msgstr "Nova problemo"
+
+msgid "NotificationEvent|New merge request"
+msgstr "Nova peto pri kunfando"
+
+msgid "NotificationEvent|New note"
+msgstr "Nova noto"
+
+msgid "NotificationEvent|Reassign issue"
+msgstr "Reatribui problemon"
+
+msgid "NotificationEvent|Reassign merge request"
+msgstr "Reatribui peton pri kunfando"
+
+msgid "NotificationEvent|Reopen issue"
+msgstr "Remalfermi problemon"
+
+msgid "NotificationEvent|Successful pipeline"
+msgstr "Sukcesa ĉenstablo"
+
+msgid "NotificationLevel|Custom"
+msgstr "Propraj"
+
+msgid "NotificationLevel|Disabled"
+msgstr "Malŝaltitaj"
+
+msgid "NotificationLevel|Global"
+msgstr "Ĝeneralaj"
+
+msgid "NotificationLevel|On mention"
+msgstr "Ĉe mencio"
+
+msgid "NotificationLevel|Participate"
+msgstr "Partoprenado"
+
+msgid "NotificationLevel|Watch"
+msgstr "Rigardado"
+
+msgid "OfSearchInADropdown|Filter"
+msgstr "Filtrilo"
+
+msgid "OpenedNDaysAgo|Opened"
+msgstr "Malfermita"
+
+msgid "Options"
+msgstr "Opcioj"
+
+msgid "Owner"
+msgstr "Posedanto"
+
+msgid "Pipeline"
+msgstr "Ĉenstablo"
+
+msgid "Pipeline Health"
+msgstr "Stato"
+
+msgid "Pipeline Schedule"
+msgstr "Ĉenstabla plano"
+
+msgid "Pipeline Schedules"
+msgstr "Ĉenstablaj planoj"
+
+msgid "PipelineSchedules|Activated"
+msgstr "Ŝaltita"
+
+msgid "PipelineSchedules|Active"
+msgstr "Ŝaltitaj"
+
+msgid "PipelineSchedules|All"
+msgstr "Ĉiuj"
+
+msgid "PipelineSchedules|Inactive"
+msgstr "Malŝaltitaj"
+
+msgid "PipelineSchedules|Next Run"
+msgstr "Sekvanta plenumo"
+
+msgid "PipelineSchedules|None"
+msgstr "Nenio"
+
+msgid "PipelineSchedules|Provide a short description for this pipeline"
+msgstr "Entajpu mallongan priskribon pri ĉi tiu ĉenstablo"
+
+msgid "PipelineSchedules|Take ownership"
+msgstr "Akiri posedon"
+
+msgid "PipelineSchedules|Target"
+msgstr "Celo"
+
+msgid "PipelineSheduleIntervalPattern|Custom"
+msgstr "Propra"
+
+msgid "Pipeline|with stage"
+msgstr "kun etapo"
+
+msgid "Pipeline|with stages"
+msgstr "kun etapoj"
+
+msgid "Project '%{project_name}' queued for deletion."
+msgstr "La projekto „%{project_name}“ estis alvicigita por forigado."
+
+msgid "Project '%{project_name}' was successfully created."
+msgstr "La projekto „%{project_name}“ estis sukcese kreita."
+
+msgid "Project '%{project_name}' was successfully updated."
+msgstr "La projekto „%{project_name}“ estis sukcese ĝisdatigita."
+
+msgid "Project '%{project_name}' will be deleted."
+msgstr "La projekto „%{project_name}“ estos forigita."
+
+msgid "Project access must be granted explicitly to each user."
+msgstr "Ĉiu uzanto devas akiri propran atingon al la projekto."
+
+msgid "Project export could not be deleted."
+msgstr "Ne eblas forigi la projektan elporton."
+
+msgid "Project export has been deleted."
+msgstr "La projekta elporto estis forigita."
+
+msgid ""
+"Project export link has expired. Please generate a new export from your "
+"project settings."
+msgstr ""
+"La ligilo por la projekta elporto eksvalidiĝis. Bonvolu krei novan elporton "
+"en la agordoj de la projekto."
+
+msgid "Project export started. A download link will be sent by email."
+msgstr ""
+"La elporto de la projekto komenciĝis. Vi ricevos ligilon per retpoŝto por "
+"elŝuti la datenoj."
+
+msgid "Project home"
+msgstr "Hejmo de la projekto"
+
+msgid "ProjectFeature|Disabled"
+msgstr "Malŝaltita"
+
+msgid "ProjectFeature|Everyone with access"
+msgstr "Ĉiu, kiu havas atingon"
+
+msgid "ProjectFeature|Only team members"
+msgstr "Nur skipanoj"
+
+msgid "ProjectFileTree|Name"
+msgstr "Nomo"
+
+msgid "ProjectLastActivity|Never"
+msgstr "Neniam"
+
+msgid "ProjectLifecycle|Stage"
+msgstr "Etapo"
+
+msgid "ProjectNetworkGraph|Graph"
+msgstr "Grafeo"
+
+msgid "Read more"
+msgstr "Legu pli"
+
+msgid "Readme"
+msgstr "LeguMin"
+
+msgid "RefSwitcher|Branches"
+msgstr "Branĉoj"
+
+msgid "RefSwitcher|Tags"
+msgstr "Etikedoj"
+
+msgid "Related Commits"
+msgstr "Rilataj enmetadoj"
+
+msgid "Related Deployed Jobs"
+msgstr "Rilataj disponigitaj taskoj"
+
+msgid "Related Issues"
+msgstr "Rilataj problemoj"
+
+msgid "Related Jobs"
+msgstr "Rilataj taskoj"
+
+msgid "Related Merge Requests"
+msgstr "Rilataj petoj pri kunfando"
+
+msgid "Related Merged Requests"
+msgstr "Rilataj aplikitaj petoj pri kunfando"
+
+msgid "Remind later"
+msgstr "Rememorigu denove"
+
+msgid "Remove project"
+msgstr "Forigi la projekton"
+
+msgid "Request Access"
+msgstr "Peti atingeblon"
+
+msgid "Revert this commit"
+msgstr "Malfari ĉi tiun enmetadon"
+
+msgid "Revert this merge request"
+msgstr "Malfari ĉi tiun peton pri kunfando"
+
+msgid "Save pipeline schedule"
+msgstr "Konservi ĉenstablan planon"
+
+msgid "Schedule a new pipeline"
+msgstr "Plani novan ĉenstablon"
+
+msgid "Scheduling Pipelines"
+msgstr "Planado de la ĉenstabloj"
+
+msgid "Search branches and tags"
+msgstr "Serĉu branĉon aŭ etikedon"
+
+msgid "Select Archive Format"
+msgstr "Elektu formaton de arkivo"
+
+msgid "Select a timezone"
+msgstr "Elektu horzonon"
+
+msgid "Select target branch"
+msgstr "Elektu celan branĉon"
+
+msgid "Set a password on your account to pull or push via %{protocol}."
+msgstr ""
+"Kreu pasvorton por via konto por ebligi al vi eltiri kaj alpuŝi per "
+"%{protocol}."
+
+msgid "Set up CI"
+msgstr "Agordi SI"
+
+msgid "Set up Koding"
+msgstr "Agordi „Koding“"
+
+msgid "Set up auto deploy"
+msgstr "Agordi aŭtomatan disponigadon"
+
+msgid "SetPasswordToCloneLink|set a password"
+msgstr "kreos pasvorton"
+
+msgid "Showing %d event"
+msgid_plural "Showing %d events"
+msgstr[0] "Estas montrata %d evento"
+msgstr[1] "Estas montrataj %d eventoj"
+
+msgid "Source code"
+msgstr "Kodo"
+
+msgid "StarProject|Star"
+msgstr "Steligi"
+
+msgid "Start a %{new_merge_request} with these changes"
+msgstr "Kreu %{new_merge_request} kun ĉi tiuj ŝanĝoj"
+
+msgid "Switch branch/tag"
+msgstr "Iri al branĉo/etikedo"
+
+msgid "Tag"
+msgid_plural "Tags"
+msgstr[0] "Etikedo"
+msgstr[1] "Etikedoj"
+
+msgid "Tags"
+msgstr "Etikedoj"
+
+msgid "Target Branch"
+msgstr "Cela branĉo"
+
+msgid ""
+"The coding stage shows the time from the first commit to creating the merge "
+"request. The data will automatically be added here once you create your "
+"first merge request."
+msgstr ""
+"La etapo de programado montras la tempon de la unua enmetado ĝis la kreado "
+"de la peto pri kunfando. La datenoj aldoniĝos aŭtomate ĉi tie post kiam vi "
+"kreas la unuan peton pri kunfando."
+
+msgid "The collection of events added to the data gathered for that stage."
+msgstr ""
+"La aro da eventoj, kiuj estas aldonitaj al la datenoj kolektitaj por la "
+"etapo."
+
+msgid "The fork relationship has been removed."
+msgstr "La rilato de disbranĉigo estis forigita."
+
+msgid ""
+"The issue stage shows the time it takes from creating an issue to assigning "
+"the issue to a milestone, or add the issue to a list on your Issue Board. "
+"Begin creating issues to see data for this stage."
+msgstr ""
+"La etapo de la problemo montras kiom la tempo pasas de la kreado de problemo "
+"ĝis la atribuado de la problemo al cela etapo de la projekto, aŭ al listo "
+"sur la problemtabulo. Komencu krei problemojn por vidi la datenojn por ĉi "
+"tiu etapo."
+
+msgid "The phase of the development lifecycle."
+msgstr "La etapo de la disvolva ciklo."
+
+msgid ""
+"The pipelines schedule runs pipelines in the future, repeatedly, for "
+"specific branches or tags. Those scheduled pipelines will inherit limited "
+"project access based on their associated user."
+msgstr ""
+"La ĉenstabla plano plenumas ĉenstablojn en la estonteco, ripete, por "
+"difinitaj branĉoj aŭ etikedoj. Tiuj planitaj ĉenstabloj heredos la limigitan "
+"atingon al la projekto de la rilata uzanto."
+
+msgid ""
+"The planning stage shows the time from the previous step to pushing your "
+"first commit. This time will be added automatically once you push your first "
+"commit."
+msgstr ""
+"La etapo de la plano montras la tempon de la antaŭa ŝtupo ĝis la alpuŝado de "
+"via unua enmetado. Ĉi tiu tempo aldoniĝos aŭtomate post kiam vi alpuŝas la "
+"unuan enmetadon."
+
+msgid ""
+"The production stage shows the total time it takes between creating an issue "
+"and deploying the code to production. The data will be automatically added "
+"once you have completed the full idea to production cycle."
+msgstr ""
+"La etapo de eldonado montras la tutan tempon de la kreado de problemo ĝis la "
+"disponigado en la publika versio. La datenoj aldoniĝos aŭtomate post kiam vi "
+"kompletigos plenan ciklon de ideo ĝis realaĵo."
+
+msgid "The project can be accessed by any logged in user."
+msgstr "Ĉiu ensalutita uzanto havas atingon al la projekto"
+
+msgid "The project can be accessed without any authentication."
+msgstr "Ĉiu povas havi atingon al la projekto, sen ensaluti"
+
+msgid "The repository for this project does not exist."
+msgstr "La deponejo por ĉi tiu projekto ne ekzistas."
+
+msgid ""
+"The review stage shows the time from creating the merge request to merging "
+"it. The data will automatically be added after you merge your first merge "
+"request."
+msgstr ""
+"La etapo de la kontrolo montras la tempon de la kreado de la peto pri "
+"kunfando ĝis ĝia aplikado. La datenoj aldoniĝos aŭtomate post kiam vi "
+"aplikos la unuan peton pri kunfando."
+
+msgid ""
+"The staging stage shows the time between merging the MR and deploying code "
+"to the production environment. The data will be automatically added once you "
+"deploy to production for the first time."
+msgstr ""
+"La etapo de preparo por eldono montras la tempon inter la aplikado de la "
+"peto pri kunfando kaj la disponigado de la kodo en la publika versio. La "
+"datenoj aldoniĝos aŭtomate post kiam vi faros la unuan disponigadon en la "
+"publika versio."
+
+msgid ""
+"The testing stage shows the time GitLab CI takes to run every pipeline for "
+"the related merge request. The data will automatically be added after your "
+"first pipeline finishes running."
+msgstr ""
+"La etapo de testado montras kiom da tempo necesas al „GitLab CI“ por plenumi "
+"ĉiujn ĉenstablojn por la rilata peto pri kunfando. La datenoj aldoniĝos "
+"aŭtomate post kiam via unua ĉenstablo finiĝos."
+
+msgid "The time taken by each data entry gathered by that stage."
+msgstr "La tempo, kiu estas necesa por ĉiu dateno kolektita de la etapo."
+
+msgid ""
+"The value lying at the midpoint of a series of observed values. E.g., "
+"between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 ="
+" 6."
+msgstr ""
+"La valoro, kiu troviĝas en la mezo de aro da rigardataj valoroj. Ekzemple: "
+"inter 3, 5 kaj 9, la mediano estas 5. Inter 3, 5, 7 kaj 8, la mediano estas "
+"(5+7)/2 = 6."
+
+msgid ""
+"This means you can not push code until you create an empty repository or "
+"import existing one."
+msgstr ""
+"Ĉi tiu signifas, ke vi ne povos alpuŝi kodon, antaŭ ol vi kreos malplenan "
+"deponejon aŭ enportos jam ekzistantan."
+
+msgid "Time before an issue gets scheduled"
+msgstr "Tempo antaŭ problemo estas planita por ellabori"
+
+msgid "Time before an issue starts implementation"
+msgstr "Tempo antaŭ la komenco de laboro super problemo"
+
+msgid "Time between merge request creation and merge/close"
+msgstr "Tempo inter la kreado de poeto pri kunfando kaj ĝia aplikado/fermado"
+
+msgid "Time until first merge request"
+msgstr "Tempo ĝis la unua peto pri kunfando"
+
+msgid "Timeago|%s days ago"
+msgstr "antaŭ %s tagoj"
+
+msgid "Timeago|%s days remaining"
+msgstr "restas %s tagoj"
+
+msgid "Timeago|%s hours remaining"
+msgstr "restas %s horoj"
+
+msgid "Timeago|%s minutes ago"
+msgstr "antaŭ %s minutoj"
+
+msgid "Timeago|%s minutes remaining"
+msgstr "restas %s minutoj"
+
+msgid "Timeago|%s months ago"
+msgstr "antaŭ %s monatoj"
+
+msgid "Timeago|%s months remaining"
+msgstr "restas %s monatoj"
+
+msgid "Timeago|%s seconds remaining"
+msgstr "restas %s sekundoj"
+
+msgid "Timeago|%s weeks ago"
+msgstr "antaŭ %s semajnoj"
+
+msgid "Timeago|%s weeks remaining"
+msgstr "restas %s semajnoj"
+
+msgid "Timeago|%s years ago"
+msgstr "antaŭ %s jaroj"
+
+msgid "Timeago|%s years remaining"
+msgstr "restas %s jaroj"
+
+msgid "Timeago|1 day remaining"
+msgstr "restas 1 tago"
+
+msgid "Timeago|1 hour remaining"
+msgstr "restas 1 horo"
+
+msgid "Timeago|1 minute remaining"
+msgstr "restas 1 minuto"
+
+msgid "Timeago|1 month remaining"
+msgstr "restas 1 monato"
+
+msgid "Timeago|1 week remaining"
+msgstr "restas 1 semajno"
+
+msgid "Timeago|1 year remaining"
+msgstr "restas 1 jaro"
+
+msgid "Timeago|Past due"
+msgstr "Malfruiĝis"
+
+msgid "Timeago|a day ago"
+msgstr "antaŭ unu tago"
+
+msgid "Timeago|a month ago"
+msgstr "antaŭ unu monato"
+
+msgid "Timeago|a week ago"
+msgstr "antaŭ unu semajno"
+
+msgid "Timeago|a while"
+msgstr "antaŭ iom da tempo"
+
+msgid "Timeago|a year ago"
+msgstr "antaŭ unu jaro"
+
+msgid "Timeago|about %s hours ago"
+msgstr "antaŭ ĉirkaŭ %s horoj"
+
+msgid "Timeago|about a minute ago"
+msgstr "antaŭ ĉirkaŭ unu minuto"
+
+msgid "Timeago|about an hour ago"
+msgstr "antaŭ ĉirkaŭ unu horo"
+
+msgid "Timeago|in %s days"
+msgstr "post %s tagoj"
+
+msgid "Timeago|in %s hours"
+msgstr "post %s horoj"
+
+msgid "Timeago|in %s minutes"
+msgstr "post %s minutoj"
+
+msgid "Timeago|in %s months"
+msgstr "post %s monatoj"
+
+msgid "Timeago|in %s seconds"
+msgstr "post %s sekundoj"
+
+msgid "Timeago|in %s weeks"
+msgstr "post %s semajnoj"
+
+msgid "Timeago|in %s years"
+msgstr "post %s jaroj"
+
+msgid "Timeago|in 1 day"
+msgstr "post 1 tago"
+
+msgid "Timeago|in 1 hour"
+msgstr "post 1 horo"
+
+msgid "Timeago|in 1 minute"
+msgstr "post 1 minuto"
+
+msgid "Timeago|in 1 month"
+msgstr "post 1 monato"
+
+msgid "Timeago|in 1 week"
+msgstr "post 1 semajno"
+
+msgid "Timeago|in 1 year"
+msgstr "post 1 jaro"
+
+msgid "Timeago|less than a minute ago"
+msgstr "antaŭ malpli ol minuto"
+
+msgid "Time|hr"
+msgid_plural "Time|hrs"
+msgstr[0] "h"
+msgstr[1] "h"
+
+msgid "Time|min"
+msgid_plural "Time|mins"
+msgstr[0] "min"
+msgstr[1] "min"
+
+msgid "Time|s"
+msgstr "s"
+
+msgid "Total Time"
+msgstr "Totala tempo"
+
+msgid "Total test time for all commits/merges"
+msgstr "Totala tempo por la testado de ĉiuj enmetadoj/kunfandoj"
+
+msgid "Unstar"
+msgstr "Malsteligi"
+
+msgid "Upload New File"
+msgstr "Alŝuti novan dosieron"
+
+msgid "Upload file"
+msgstr "Alŝuti dosieron"
+
+msgid "UploadLink|click to upload"
+msgstr "alklaku por alŝuti"
+
+msgid "Use your global notification setting"
+msgstr "Uzi vian ĝeneralan agordon pri la sciigoj"
+
+msgid "View open merge request"
+msgstr "Vidi la malfermitan peton pri kunfando"
+
+msgid "VisibilityLevel|Internal"
+msgstr "Interna"
+
+msgid "VisibilityLevel|Private"
+msgstr "Privata"
+
+msgid "VisibilityLevel|Public"
+msgstr "Publika"
+
+msgid "Want to see the data? Please ask an administrator for access."
+msgstr ""
+"Ĉu vi volas vidi la datenojn? Bonvolu peti atingeblon de administranto."
+
+msgid "We don't have enough data to show this stage."
+msgstr "Ne estas sufiĉe da datenoj por montri ĉi tiun etapon."
+
+msgid "Withdraw Access Request"
+msgstr "Nuligi la peton pri atingeblo"
+
+msgid ""
+"You are going to remove %{project_name_with_namespace}.\n"
+"Removed project CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr ""
+"Vi forigos „%{project_name_with_namespace}“.\n"
+"Oni NE POVAS malfari la forigon de projekto!\n"
+"Ĉu vi estas ABSOLUTE certa?"
+
+msgid ""
+"You are going to remove the fork relationship to source project "
+"%{forked_from_project}. Are you ABSOLUTELY sure?"
+msgstr ""
+"Vi forigos la rilaton de la disbranĉigo al la originala projekto, "
+"„%{forked_from_project}“. Ĉu vi estas ABSOLUTE certa?"
+
+msgid ""
+"You are going to transfer %{project_name_with_namespace} to another owner. "
+"Are you ABSOLUTELY sure?"
+msgstr ""
+"Vi transigos „%{project_name_with_namespace}“ al alia posedanto. Ĉu vi estas "
+"ABSOLUTE certa?"
+
+msgid "You can only add files when you are on a branch"
+msgstr "Oni povas aldoni dosierojn nur kiam oni estas en branĉo"
+
+msgid "You have reached your project limit"
+msgstr "Vi ne povas krei pliajn projektojn"
+
+msgid "You must sign in to star a project"
+msgstr "Oni devas ensaluti por steligi projekton"
+
+msgid "You need permission."
+msgstr "VI bezonas permeson."
+
+msgid "You will not get any notifications via email"
+msgstr "VI ne ricevos sciigojn per retpoŝto"
+
+msgid "You will only receive notifications for the events you choose"
+msgstr "Vi ricevos sciigojn nur por la eventoj elektitaj de vi"
+
+msgid ""
+"You will only receive notifications for threads you have participated in"
+msgstr "Vi ricevos sciigojn nur por la fadenoj, en kiuj vi partoprenis"
+
+msgid "You will receive notifications for any activity"
+msgstr "Vi ricevos sciigojn por ĉiu ago"
+
+msgid ""
+"You will receive notifications only for comments in which you were "
+"@mentioned"
+msgstr "Vi ricevos sciigojn nur por komentoj, en kiuj vi estas @menciita"
+
+msgid ""
+"You won't be able to pull or push project code via %{protocol} until you "
+"%{set_password_link} on your account"
+msgstr ""
+"Vi ne povos eltiri aŭ alpuŝi kodon per %{protocol} antaŭ ol vi "
+"%{set_password_link} por via konto"
+
+msgid ""
+"You won't be able to pull or push project code via SSH until you "
+"%{add_ssh_key_link} to your profile"
+msgstr ""
+"Vi ne povos eltiri aŭ alpuŝi kodon per SSH antaŭ ol vi %{add_ssh_key_link} "
+"al via profilo"
+
+msgid "Your name"
+msgstr "Via nomo"
+
+msgid "day"
+msgid_plural "days"
+msgstr[0] "tago"
+msgstr[1] "tagoj"
+
+msgid "new merge request"
+msgstr "novan peton pri kunfando"
+
+msgid "notification emails"
+msgstr "sciigoj per retpoŝto"
+
+msgid "parent"
+msgid_plural "parents"
+msgstr[0] "patro"
+msgstr[1] "patroj"
+
diff --git a/locale/eo/gitlab.po.time_stamp b/locale/eo/gitlab.po.time_stamp
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/locale/es/gitlab.po b/locale/es/gitlab.po
index 78d28d69885f3b2b7aa81eb73709c684891d88d9..cec086b871c55810f14d054646920602f12c59a6 100644
--- a/locale/es/gitlab.po
+++ b/locale/es/gitlab.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: gitlab 1.0.0\n"
 "Report-Msgid-Bugs-To: \n"
-"PO-Revision-Date: 2017-06-07 12:29-0500\n"
+"PO-Revision-Date: 2017-06-21 12:09-0500\n"
 "Language-Team: Spanish\n"
 "Language: es\n"
 "MIME-Version: 1.0\n"
@@ -17,9 +17,25 @@ msgstr ""
 "Last-Translator: Bob Van Landuyt <bob@gitlab.com>\n"
 "X-Generator: Poedit 2.0.2\n"
 
+msgid "%d additional commit has been omitted to prevent performance issues."
+msgid_plural "%d additional commits have been omitted to prevent performance issues."
+msgstr[0] "%d cambio adicional ha sido omitido para evitar problemas de rendimiento."
+msgstr[1] "%d cambios adicionales han sido omitidos para evitar problemas de rendimiento."
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] "%d cambio"
+msgstr[1] "%d cambios"
+
+msgid "%{commit_author_link} committed %{commit_timeago}"
+msgstr "%{commit_author_link} cambió %{commit_timeago}"
+
 msgid "About auto deploy"
 msgstr "Acerca del auto despliegue"
 
+msgid "Active"
+msgstr "Activo"
+
 msgid "Activity"
 msgstr "Actividad"
 
@@ -39,7 +55,13 @@ msgid "Add new directory"
 msgstr "Agregar nuevo directorio"
 
 msgid "Archived project! Repository is read-only"
-msgstr "¡Proyecto archivado! El repositorio es de sólo lectura"
+msgstr "¡Proyecto archivado! El repositorio es de solo lectura"
+
+msgid "Are you sure you want to delete this pipeline schedule?"
+msgstr "¿Estás seguro que deseas eliminar esta programación del pipeline?"
+
+msgid "Attach a file by drag &amp; drop or %{upload_link}"
+msgstr "Adjunte un archivo arrastrando &amp; soltando o %{upload_link}"
 
 msgid "Branch"
 msgid_plural "Branches"
@@ -49,21 +71,60 @@ msgstr[1] "Ramas"
 msgid "Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}"
 msgstr "La rama <strong>%{branch_name}</strong> fue creada. Para configurar el auto despliegue, escoge una plantilla Yaml para GitLab CI y envía tus cambios. %{link_to_autodeploy_doc}"
 
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr "Buscar ramas"
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr "Cambiar rama"
+
 msgid "Branches"
 msgstr "Ramas"
 
+msgid "Browse Directory"
+msgstr "Examinar directorio"
+
+msgid "Browse File"
+msgstr "Examinar archivo"
+
+msgid "Browse Files"
+msgstr "Examinar archivos"
+
+msgid "Browse files"
+msgstr "Examinar archivos"
+
 msgid "ByAuthor|by"
 msgstr "por"
 
 msgid "CI configuration"
 msgstr "Configuración de CI"
 
+msgid "Cancel"
+msgstr "Cancelar"
+
+msgid "ChangeTypeActionLabel|Pick into branch"
+msgstr "Escoger en la rama"
+
+msgid "ChangeTypeActionLabel|Revert in branch"
+msgstr "Revertir en la rama"
+
+msgid "ChangeTypeAction|Cherry-pick"
+msgstr "Cherry-pick"
+
+msgid "ChangeTypeAction|Revert"
+msgstr "Revertir"
+
 msgid "Changelog"
 msgstr "Changelog"
 
 msgid "Charts"
 msgstr "Gráficos"
 
+msgid "Cherry-pick this commit"
+msgstr "Escoger este cambio"
+
+msgid "Cherry-pick this merge request"
+msgstr "Escoger esta solicitud de fusión"
+
 msgid "CiStatusLabel|canceled"
 msgstr "cancelado"
 
@@ -71,7 +132,7 @@ msgid "CiStatusLabel|created"
 msgstr "creado"
 
 msgid "CiStatusLabel|failed"
-msgstr "fallado"
+msgstr "fallido"
 
 msgid "CiStatusLabel|manual action"
 msgstr "acción manual"
@@ -123,15 +184,27 @@ msgid_plural "Commits"
 msgstr[0] "Cambio"
 msgstr[1] "Cambios"
 
+msgid "Commit message"
+msgstr "Mensaje del cambio"
+
+msgid "CommitBoxTitle|Commit"
+msgstr "Cambio"
+
 msgid "CommitMessage|Add %{file_name}"
 msgstr "Agregar %{file_name}"
 
 msgid "Commits"
 msgstr "Cambios"
 
+msgid "Commits feed"
+msgstr "Feed de cambios"
+
 msgid "Commits|History"
 msgstr "Historial"
 
+msgid "Committed by"
+msgstr "Enviado por"
+
 msgid "Compare"
 msgstr "Comparar"
 
@@ -159,9 +232,21 @@ msgstr "Crear repositorio vacío"
 msgid "Create merge request"
 msgstr "Crear solicitud de fusión"
 
+msgid "Create new..."
+msgstr "Crear nuevo..."
+
 msgid "CreateNewFork|Fork"
 msgstr "Bifurcar"
 
+msgid "CreateTag|Tag"
+msgstr "Etiqueta"
+
+msgid "Cron Timezone"
+msgstr "Zona horaria del Cron"
+
+msgid "Cron syntax"
+msgstr "Sintaxis de Cron"
+
 msgid "Custom notification events"
 msgstr "Eventos de notificaciones personalizadas"
 
@@ -195,17 +280,29 @@ msgstr "Puesta en escena"
 msgid "CycleAnalyticsStage|Test"
 msgstr "Pruebas"
 
+msgid "Define a custom pattern with cron syntax"
+msgstr "Definir un patrón personalizado con la sintaxis de cron"
+
+msgid "Delete"
+msgstr "Eliminar"
+
 msgid "Deploy"
 msgid_plural "Deploys"
 msgstr[0] "Despliegue"
 msgstr[1] "Despliegues"
 
+msgid "Description"
+msgstr "Descripción"
+
 msgid "Directory name"
 msgstr "Nombre del directorio"
 
 msgid "Don't show again"
 msgstr "No mostrar de nuevo"
 
+msgid "Download"
+msgstr "Descargar"
+
 msgid "Download tar"
 msgstr "Descargar tar"
 
@@ -221,12 +318,42 @@ msgstr "Descargar zip"
 msgid "DownloadArtifacts|Download"
 msgstr "Descargar"
 
+msgid "DownloadCommit|Email Patches"
+msgstr "Parches por correo electrónico"
+
+msgid "DownloadCommit|Plain Diff"
+msgstr "Diferencias en texto plano"
+
 msgid "DownloadSource|Download"
 msgstr "Descargar"
 
+msgid "Edit"
+msgstr "Editar"
+
+msgid "Edit Pipeline Schedule %{id}"
+msgstr "Editar Programación del Pipeline %{id}"
+
+msgid "Every day (at 4:00am)"
+msgstr "Todos los días (a las 4:00 am)"
+
+msgid "Every month (on the 1st at 4:00am)"
+msgstr "Todos los meses (el día 1 a las 4:00 am)"
+
+msgid "Every week (Sundays at 4:00am)"
+msgstr "Todas las semanas (domingos a las 4:00 am)"
+
+msgid "Failed to change the owner"
+msgstr "Error al cambiar el propietario"
+
+msgid "Failed to remove the pipeline schedule"
+msgstr "Error al eliminar la programación del pipeline"
+
 msgid "Files"
 msgstr "Archivos"
 
+msgid "Filter by commit message"
+msgstr "Filtrar por mensaje del cambio"
+
 msgid "Find by path"
 msgstr "Buscar por ruta"
 
@@ -239,12 +366,14 @@ msgstr "Primer"
 msgid "FirstPushedBy|pushed by"
 msgstr "enviado por"
 
+msgid "Fork"
+msgid_plural "Forks"
+msgstr[0] "Bifurcación"
+msgstr[1] "Bifurcaciones"
+
 msgid "ForkedFromProjectPath|Forked from"
 msgstr "Bifurcado de"
 
-msgid "Forks"
-msgstr "Bifurcaciones"
-
 msgid "From issue creation until deploy to production"
 msgstr "Desde la creación de la incidencia hasta el despliegue a producción"
 
@@ -266,6 +395,9 @@ msgstr "Servicio de limpieza iniciado con éxito"
 msgid "Import repository"
 msgstr "Importar repositorio"
 
+msgid "Interval Pattern"
+msgstr "Patrón de intervalo"
+
 msgid "Introducing Cycle Analytics"
 msgstr "Introducción a Cycle Analytics"
 
@@ -280,12 +412,21 @@ msgid_plural "Last %d days"
 msgstr[0] "Último %d día"
 msgstr[1] "Últimos %d días"
 
+msgid "Last Pipeline"
+msgstr "Último Pipeline"
+
 msgid "Last Update"
 msgstr "Última actualización"
 
 msgid "Last commit"
 msgstr "Último cambio"
 
+msgid "Learn more in the"
+msgstr "Más información en la"
+
+msgid "Learn more in the|pipeline schedules documentation"
+msgstr "documentación sobre la programación de pipelines"
+
 msgid "Leave group"
 msgstr "Abandonar grupo"
 
@@ -308,6 +449,9 @@ msgid_plural "New Issues"
 msgstr[0] "Nueva incidencia"
 msgstr[1] "Nuevas incidencias"
 
+msgid "New Pipeline Schedule"
+msgstr "Nueva Programación del Pipeline"
+
 msgid "New branch"
 msgstr "Nueva rama"
 
@@ -323,6 +467,9 @@ msgstr "Nueva incidencia"
 msgid "New merge request"
 msgstr "Nueva solicitud de fusión"
 
+msgid "New schedule"
+msgstr "Nueva programación"
+
 msgid "New snippet"
 msgstr "Nuevo fragmento de código"
 
@@ -332,6 +479,9 @@ msgstr "Nueva etiqueta"
 msgid "No repository"
 msgstr "No hay repositorio"
 
+msgid "No schedules"
+msgstr "No hay programaciones"
+
 msgid "Not available"
 msgstr "No disponible"
 
@@ -392,12 +542,66 @@ msgstr "Participación"
 msgid "NotificationLevel|Watch"
 msgstr "Vigilancia"
 
+msgid "OfSearchInADropdown|Filter"
+msgstr "Filtrar"
+
 msgid "OpenedNDaysAgo|Opened"
 msgstr "Abierto"
 
+msgid "Options"
+msgstr "Opciones"
+
+msgid "Owner"
+msgstr "Propietario"
+
+msgid "Pipeline"
+msgstr "Pipeline"
+
 msgid "Pipeline Health"
 msgstr "Estado del Pipeline"
 
+msgid "Pipeline Schedule"
+msgstr "Programación del Pipeline"
+
+msgid "Pipeline Schedules"
+msgstr "Programaciones de los Pipelines"
+
+msgid "PipelineSchedules|Activated"
+msgstr "Activado"
+
+msgid "PipelineSchedules|Active"
+msgstr "Activos"
+
+msgid "PipelineSchedules|All"
+msgstr "Todos"
+
+msgid "PipelineSchedules|Inactive"
+msgstr "Inactivos"
+
+msgid "PipelineSchedules|Next Run"
+msgstr "Próxima Ejecución"
+
+msgid "PipelineSchedules|None"
+msgstr "Ninguno"
+
+msgid "PipelineSchedules|Provide a short description for this pipeline"
+msgstr "Proporcione una breve descripción para este pipeline"
+
+msgid "PipelineSchedules|Take ownership"
+msgstr "Tomar posesión"
+
+msgid "PipelineSchedules|Target"
+msgstr "Destino"
+
+msgid "PipelineSheduleIntervalPattern|Custom"
+msgstr "Personalizado"
+
+msgid "Pipeline|with stage"
+msgstr "con etapa"
+
+msgid "Pipeline|with stages"
+msgstr "con etapas"
+
 msgid "Project '%{project_name}' queued for deletion."
 msgstr "Proyecto ‘%{project_name}’ en cola para eliminación."
 
@@ -453,7 +657,7 @@ msgid "Read more"
 msgstr "Leer más"
 
 msgid "Readme"
-msgstr "Readme"
+msgstr "Léeme"
 
 msgid "RefSwitcher|Branches"
 msgstr "Ramas"
@@ -488,14 +692,35 @@ msgstr "Eliminar proyecto"
 msgid "Request Access"
 msgstr "Solicitar acceso"
 
+msgid "Revert this commit"
+msgstr "Revertir este cambio"
+
+msgid "Revert this merge request"
+msgstr "Revertir esta solicitud de fusión"
+
+msgid "Save pipeline schedule"
+msgstr "Guardar programación del pipeline"
+
+msgid "Schedule a new pipeline"
+msgstr "Programar un nuevo pipeline"
+
+msgid "Scheduling Pipelines"
+msgstr "Programación de Pipelines"
+
 msgid "Search branches and tags"
 msgstr "Buscar ramas y etiquetas"
 
 msgid "Select Archive Format"
 msgstr "Seleccionar formato de archivo"
 
-msgid "Set a password on your account to pull or push via %{protocol}"
-msgstr "Establezca una contraseña en su cuenta para actualizar o enviar a través de% {protocol}"
+msgid "Select a timezone"
+msgstr "Selecciona una zona horaria"
+
+msgid "Select target branch"
+msgstr "Selecciona una rama de destino"
+
+msgid "Set a password on your account to pull or push via %{protocol}."
+msgstr "Establezca una contraseña en su cuenta para actualizar o enviar a través de %{protocol}."
 
 msgid "Set up CI"
 msgstr "Configurar CI"
@@ -520,6 +745,9 @@ msgstr "Código fuente"
 msgid "StarProject|Star"
 msgstr "Destacar"
 
+msgid "Start a %{new_merge_request} with these changes"
+msgstr "Iniciar una %{new_merge_request} con estos cambios"
+
 msgid "Switch branch/tag"
 msgstr "Cambiar rama/etiqueta"
 
@@ -531,6 +759,9 @@ msgstr[1] "Etiquetas"
 msgid "Tags"
 msgstr "Etiquetas"
 
+msgid "Target Branch"
+msgstr "Rama de destino"
+
 msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request."
 msgstr "La etapa de desarrollo muestra el tiempo desde el primer cambio hasta la creación de la solicitud de fusión. Los datos serán automáticamente incorporados aquí una vez creada tu primera solicitud de fusión."
 
@@ -546,6 +777,9 @@ msgstr "La etapa de incidencia muestra el tiempo que toma desde la creación de
 msgid "The phase of the development lifecycle."
 msgstr "La etapa del ciclo de vida de desarrollo."
 
+msgid "The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user."
+msgstr "La programación de pipelines ejecuta pipelines en el futuro, repetidamente, para ramas o etiquetas específicas. Los pipelines programados heredarán acceso limitado al proyecto basado en su usuario asociado."
+
 msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit."
 msgstr "La etapa de planificación muestra el tiempo desde el paso anterior hasta el envío de tu primera confirmación. Este tiempo se añadirá automáticamente una vez que usted envíe el primer cambio."
 
@@ -652,16 +886,16 @@ msgid "Timeago|a day ago"
 msgstr "hace un día"
 
 msgid "Timeago|a month ago"
-msgstr "hace 1 mes"
+msgstr "hace un mes"
 
 msgid "Timeago|a week ago"
-msgstr "hace 1 semana"
+msgstr "hace una semana"
 
 msgid "Timeago|a while"
 msgstr "hace un momento"
 
 msgid "Timeago|a year ago"
-msgstr "hace 1 año"
+msgstr "hace un año"
 
 msgid "Timeago|about %s hours ago"
 msgstr "hace alrededor de %s horas"
@@ -742,9 +976,15 @@ msgstr "Subir nuevo archivo"
 msgid "Upload file"
 msgstr "Subir archivo"
 
+msgid "UploadLink|click to upload"
+msgstr "Hacer clic para subir"
+
 msgid "Use your global notification setting"
 msgstr "Utiliza tu configuración de notificación global"
 
+msgid "View open merge request"
+msgstr "Ver solicitud de fusión abierta"
+
 msgid "VisibilityLevel|Internal"
 msgstr "Interno"
 
@@ -772,14 +1012,17 @@ msgstr ""
 "¡El proyecto eliminado NO puede ser restaurado!\n"
 "¿Estás TOTALMENTE seguro?"
 
-msgid "You are going to remove the fork relationship to source project %{forked_from_project}.  Are you ABSOLUTELY sure?"
+msgid "You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?"
 msgstr "Vas a eliminar el enlace de la bifurcación con el proyecto original %{forked_from_project}. ¿Estás TOTALMENTE seguro?"
 
 msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?"
 msgstr "Vas a transferir %{project_name_with_namespace} a otro propietario. ¿Estás TOTALMENTE seguro?"
 
 msgid "You can only add files when you are on a branch"
-msgstr "Sólo puede agregar archivos cuando estas en una rama"
+msgstr "Solo puedes agregar archivos cuando estás en una rama"
+
+msgid "You have reached your project limit"
+msgstr "Has alcanzado el límite de tu proyecto"
 
 msgid "You must sign in to star a project"
 msgstr "Debes iniciar sesión para destacar un proyecto"
@@ -797,10 +1040,10 @@ msgid "You will only receive notifications for threads you have participated in"
 msgstr "Solo recibirás notificaciones de los temas en los que has participado"
 
 msgid "You will receive notifications for any activity"
-msgstr "Recibirás notificaciones para cualquier actividad"
+msgstr "Recibirás notificaciones por cualquier actividad"
 
 msgid "You will receive notifications only for comments in which you were @mentioned"
-msgstr "Recibirás notificaciones sólo para los comentarios en los que se te mencionó"
+msgstr "Recibirás notificaciones solo para los comentarios en los que se te mencionó"
 
 msgid "You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account"
 msgstr "No podrás actualizar o enviar código al proyecto a través de %{protocol} hasta que %{set_password_link} en tu cuenta"
@@ -811,13 +1054,18 @@ msgstr "No podrás actualizar o enviar código al proyecto a través de SSH hast
 msgid "Your name"
 msgstr "Tu nombre"
 
-msgid "committed"
-msgstr "cambió"
-
 msgid "day"
 msgid_plural "days"
 msgstr[0] "día"
 msgstr[1] "días"
 
+msgid "new merge request"
+msgstr "nueva solicitud de fusión"
+
 msgid "notification emails"
 msgstr "correos electrónicos de notificación"
+
+msgid "parent"
+msgid_plural "parents"
+msgstr[0] "padre"
+msgstr[1] "padres"
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 050f6c446c139992baa45288110f2195b06f8225..9f1caeddaa7b23b380ce75d8de38a6a875528335 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -8,8 +8,8 @@ msgid ""
 msgstr ""
 "Project-Id-Version: gitlab 1.0.0\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-06-07 21:22+0200\n"
-"PO-Revision-Date: 2017-06-07 21:22+0200\n"
+"POT-Creation-Date: 2017-06-28 13:32+0200\n"
+"PO-Revision-Date: 2017-06-28 13:32+0200\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
 "Language: \n"
@@ -18,207 +18,801 @@ msgstr ""
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
 
+msgid "%d additional commit has been omitted to prevent performance issues."
+msgid_plural "%d additional commits have been omitted to prevent performance issues."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{commit_author_link} committed %{commit_timeago}"
+msgstr ""
+
+msgid "1 pipeline"
+msgid_plural "%d pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "A collection of graphs regarding Continuous Integration"
+msgstr ""
+
+msgid "About auto deploy"
+msgstr ""
+
+msgid "Active"
+msgstr ""
+
+msgid "Activity"
+msgstr ""
+
+msgid "Add Changelog"
+msgstr ""
+
+msgid "Add Contribution guide"
+msgstr ""
+
+msgid "Add License"
+msgstr ""
+
+msgid "Add an SSH key to your profile to pull or push via SSH."
+msgstr ""
+
+msgid "Add new directory"
+msgstr ""
+
+msgid "Archived project! Repository is read-only"
+msgstr ""
+
 msgid "Are you sure you want to delete this pipeline schedule?"
 msgstr ""
 
-msgid "ByAuthor|by"
+msgid "Attach a file by drag &amp; drop or %{upload_link}"
+msgstr ""
+
+msgid "Branch"
+msgid_plural "Branches"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}"
+msgstr ""
+
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr ""
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr ""
+
+msgid "Branches"
+msgstr ""
+
+msgid "Browse Directory"
+msgstr ""
+
+msgid "Browse File"
+msgstr ""
+
+msgid "Browse Files"
+msgstr ""
+
+msgid "Browse files"
+msgstr ""
+
+msgid "ByAuthor|by"
+msgstr ""
+
+msgid "CI configuration"
+msgstr ""
+
+msgid "Cancel"
+msgstr ""
+
+msgid "ChangeTypeActionLabel|Pick into branch"
+msgstr ""
+
+msgid "ChangeTypeActionLabel|Revert in branch"
+msgstr ""
+
+msgid "ChangeTypeAction|Cherry-pick"
+msgstr ""
+
+msgid "ChangeTypeAction|Revert"
+msgstr ""
+
+msgid "Changelog"
+msgstr ""
+
+msgid "Charts"
+msgstr ""
+
+msgid "Cherry-pick this commit"
+msgstr ""
+
+msgid "Cherry-pick this merge request"
+msgstr ""
+
+msgid "CiStatusLabel|canceled"
+msgstr ""
+
+msgid "CiStatusLabel|created"
+msgstr ""
+
+msgid "CiStatusLabel|failed"
+msgstr ""
+
+msgid "CiStatusLabel|manual action"
+msgstr ""
+
+msgid "CiStatusLabel|passed"
+msgstr ""
+
+msgid "CiStatusLabel|passed with warnings"
+msgstr ""
+
+msgid "CiStatusLabel|pending"
+msgstr ""
+
+msgid "CiStatusLabel|skipped"
+msgstr ""
+
+msgid "CiStatusLabel|waiting for manual action"
+msgstr ""
+
+msgid "CiStatusText|blocked"
+msgstr ""
+
+msgid "CiStatusText|canceled"
+msgstr ""
+
+msgid "CiStatusText|created"
+msgstr ""
+
+msgid "CiStatusText|failed"
+msgstr ""
+
+msgid "CiStatusText|manual"
+msgstr ""
+
+msgid "CiStatusText|passed"
+msgstr ""
+
+msgid "CiStatusText|pending"
+msgstr ""
+
+msgid "CiStatusText|skipped"
+msgstr ""
+
+msgid "CiStatus|running"
+msgstr ""
+
+msgid "Commit"
+msgid_plural "Commits"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Commit duration in minutes for last 30 commits"
+msgstr ""
+
+msgid "Commit message"
+msgstr ""
+
+msgid "CommitBoxTitle|Commit"
+msgstr ""
+
+msgid "CommitMessage|Add %{file_name}"
+msgstr ""
+
+msgid "Commits"
+msgstr ""
+
+msgid "Commits feed"
+msgstr ""
+
+msgid "Commits|History"
+msgstr ""
+
+msgid "Committed by"
+msgstr ""
+
+msgid "Compare"
+msgstr ""
+
+msgid "Contribution guide"
+msgstr ""
+
+msgid "Contributors"
+msgstr ""
+
+msgid "Copy URL to clipboard"
+msgstr ""
+
+msgid "Copy commit SHA to clipboard"
+msgstr ""
+
+msgid "Create New Directory"
+msgstr ""
+
+msgid "Create a personal access token on your account to pull or push via %{protocol}."
+msgstr ""
+
+msgid "Create directory"
+msgstr ""
+
+msgid "Create empty bare repository"
+msgstr ""
+
+msgid "Create merge request"
+msgstr ""
+
+msgid "Create new..."
+msgstr ""
+
+msgid "CreateNewFork|Fork"
+msgstr ""
+
+msgid "CreateTag|Tag"
+msgstr ""
+
+msgid "CreateTokenToCloneLink|create a personal access token"
+msgstr ""
+
+msgid "Cron Timezone"
+msgstr ""
+
+msgid "Cron syntax"
+msgstr ""
+
+msgid "Custom notification events"
+msgstr ""
+
+msgid "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 %{notification_link}."
+msgstr ""
+
+msgid "Cycle Analytics"
+msgstr ""
+
+msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project."
+msgstr ""
+
+msgid "CycleAnalyticsStage|Code"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Issue"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Plan"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Production"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Review"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Staging"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Test"
+msgstr ""
+
+msgid "Define a custom pattern with cron syntax"
+msgstr ""
+
+msgid "Delete"
+msgstr ""
+
+msgid "Deploy"
+msgid_plural "Deploys"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Description"
+msgstr ""
+
+msgid "Directory name"
+msgstr ""
+
+msgid "Don't show again"
+msgstr ""
+
+msgid "Download"
+msgstr ""
+
+msgid "Download tar"
+msgstr ""
+
+msgid "Download tar.bz2"
+msgstr ""
+
+msgid "Download tar.gz"
+msgstr ""
+
+msgid "Download zip"
+msgstr ""
+
+msgid "DownloadArtifacts|Download"
+msgstr ""
+
+msgid "DownloadCommit|Email Patches"
+msgstr ""
+
+msgid "DownloadCommit|Plain Diff"
+msgstr ""
+
+msgid "DownloadSource|Download"
+msgstr ""
+
+msgid "Edit"
+msgstr ""
+
+msgid "Edit Pipeline Schedule %{id}"
+msgstr ""
+
+msgid "Every day (at 4:00am)"
+msgstr ""
+
+msgid "Every month (on the 1st at 4:00am)"
+msgstr ""
+
+msgid "Every week (Sundays at 4:00am)"
+msgstr ""
+
+msgid "Failed to change the owner"
+msgstr ""
+
+msgid "Failed to remove the pipeline schedule"
+msgstr ""
+
+msgid "Files"
+msgstr ""
+
+msgid "Filter by commit message"
+msgstr ""
+
+msgid "Find by path"
+msgstr ""
+
+msgid "Find file"
+msgstr ""
+
+msgid "FirstPushedBy|First"
+msgstr ""
+
+msgid "FirstPushedBy|pushed by"
+msgstr ""
+
+msgid "Fork"
+msgid_plural "Forks"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ForkedFromProjectPath|Forked from"
+msgstr ""
+
+msgid "From issue creation until deploy to production"
+msgstr ""
+
+msgid "From merge request merge until deploy to production"
+msgstr ""
+
+msgid "Go to your fork"
+msgstr ""
+
+msgid "GoToYourFork|Fork"
+msgstr ""
+
+msgid "Home"
+msgstr ""
+
+msgid "Housekeeping successfully started"
+msgstr ""
+
+msgid "Import repository"
+msgstr ""
+
+msgid "Interval Pattern"
+msgstr ""
+
+msgid "Introducing Cycle Analytics"
+msgstr ""
+
+msgid "Jobs for last month"
+msgstr ""
+
+msgid "Jobs for last week"
+msgstr ""
+
+msgid "Jobs for last year"
+msgstr ""
+
+msgid "LFSStatus|Disabled"
+msgstr ""
+
+msgid "LFSStatus|Enabled"
+msgstr ""
+
+msgid "Last %d day"
+msgid_plural "Last %d days"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Last Pipeline"
+msgstr ""
+
+msgid "Last Update"
+msgstr ""
+
+msgid "Last commit"
+msgstr ""
+
+msgid "Learn more in the"
+msgstr ""
+
+msgid "Learn more in the|pipeline schedules documentation"
+msgstr ""
+
+msgid "Leave group"
+msgstr ""
+
+msgid "Leave project"
+msgstr ""
+
+msgid "Limited to showing %d event at most"
+msgid_plural "Limited to showing %d events at most"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Median"
+msgstr ""
+
+msgid "MissingSSHKeyWarningLink|add an SSH key"
+msgstr ""
+
+msgid "New Issue"
+msgid_plural "New Issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "New Pipeline Schedule"
+msgstr ""
+
+msgid "New branch"
+msgstr ""
+
+msgid "New directory"
+msgstr ""
+
+msgid "New file"
+msgstr ""
+
+msgid "New issue"
+msgstr ""
+
+msgid "New merge request"
+msgstr ""
+
+msgid "New schedule"
+msgstr ""
+
+msgid "New snippet"
+msgstr ""
+
+msgid "New tag"
+msgstr ""
+
+msgid "No repository"
+msgstr ""
+
+msgid "No schedules"
+msgstr ""
+
+msgid "Not available"
+msgstr ""
+
+msgid "Not enough data"
+msgstr ""
+
+msgid "Notification events"
+msgstr ""
+
+msgid "NotificationEvent|Close issue"
+msgstr ""
+
+msgid "NotificationEvent|Close merge request"
+msgstr ""
+
+msgid "NotificationEvent|Failed pipeline"
+msgstr ""
+
+msgid "NotificationEvent|Merge merge request"
+msgstr ""
+
+msgid "NotificationEvent|New issue"
+msgstr ""
+
+msgid "NotificationEvent|New merge request"
+msgstr ""
+
+msgid "NotificationEvent|New note"
+msgstr ""
+
+msgid "NotificationEvent|Reassign issue"
+msgstr ""
+
+msgid "NotificationEvent|Reassign merge request"
+msgstr ""
+
+msgid "NotificationEvent|Reopen issue"
+msgstr ""
+
+msgid "NotificationEvent|Successful pipeline"
+msgstr ""
+
+msgid "NotificationLevel|Custom"
+msgstr ""
+
+msgid "NotificationLevel|Disabled"
+msgstr ""
+
+msgid "NotificationLevel|Global"
+msgstr ""
+
+msgid "NotificationLevel|On mention"
+msgstr ""
+
+msgid "NotificationLevel|Participate"
+msgstr ""
+
+msgid "NotificationLevel|Watch"
+msgstr ""
+
+msgid "OfSearchInADropdown|Filter"
+msgstr ""
+
+msgid "OpenedNDaysAgo|Opened"
+msgstr ""
+
+msgid "Options"
+msgstr ""
+
+msgid "Owner"
+msgstr ""
+
+msgid "Pipeline"
+msgstr ""
+
+msgid "Pipeline Health"
+msgstr ""
+
+msgid "Pipeline Schedule"
+msgstr ""
+
+msgid "Pipeline Schedules"
+msgstr ""
+
+msgid "PipelineCharts|Failed:"
+msgstr ""
+
+msgid "PipelineCharts|Overall statistics"
+msgstr ""
+
+msgid "PipelineCharts|Success ratio:"
+msgstr ""
+
+msgid "PipelineCharts|Successful:"
+msgstr ""
+
+msgid "PipelineCharts|Total:"
+msgstr ""
+
+msgid "PipelineSchedules|Activated"
+msgstr ""
+
+msgid "PipelineSchedules|Active"
+msgstr ""
+
+msgid "PipelineSchedules|All"
+msgstr ""
+
+msgid "PipelineSchedules|Inactive"
+msgstr ""
+
+msgid "PipelineSchedules|Next Run"
 msgstr ""
 
-msgid "Cancel"
+msgid "PipelineSchedules|None"
 msgstr ""
 
-msgid "Commit"
-msgid_plural "Commits"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "Cron Timezone"
+msgid "PipelineSchedules|Provide a short description for this pipeline"
 msgstr ""
 
-msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project."
+msgid "PipelineSchedules|Take ownership"
 msgstr ""
 
-msgid "CycleAnalyticsStage|Code"
+msgid "PipelineSchedules|Target"
 msgstr ""
 
-msgid "CycleAnalyticsStage|Issue"
+msgid "PipelineSheduleIntervalPattern|Custom"
 msgstr ""
 
-msgid "CycleAnalyticsStage|Plan"
+msgid "Pipelines"
 msgstr ""
 
-msgid "CycleAnalyticsStage|Production"
+msgid "Pipelines charts"
 msgstr ""
 
-msgid "CycleAnalyticsStage|Review"
+msgid "Pipeline|all"
 msgstr ""
 
-msgid "CycleAnalyticsStage|Staging"
+msgid "Pipeline|success"
 msgstr ""
 
-msgid "CycleAnalyticsStage|Test"
+msgid "Pipeline|with stage"
 msgstr ""
 
-msgid "Delete"
+msgid "Pipeline|with stages"
 msgstr ""
 
-msgid "Deploy"
-msgid_plural "Deploys"
-msgstr[0] ""
-msgstr[1] ""
+msgid "Project '%{project_name}' queued for deletion."
+msgstr ""
 
-msgid "Description"
+msgid "Project '%{project_name}' was successfully created."
 msgstr ""
 
-msgid "Edit"
+msgid "Project '%{project_name}' was successfully updated."
 msgstr ""
 
-msgid "Edit Pipeline Schedule %{id}"
+msgid "Project '%{project_name}' will be deleted."
 msgstr ""
 
-msgid "Failed to change the owner"
+msgid "Project access must be granted explicitly to each user."
 msgstr ""
 
-msgid "Failed to remove the pipeline schedule"
+msgid "Project export could not be deleted."
 msgstr ""
 
-msgid "Filter"
+msgid "Project export has been deleted."
 msgstr ""
 
-msgid "FirstPushedBy|First"
+msgid "Project export link has expired. Please generate a new export from your project settings."
 msgstr ""
 
-msgid "FirstPushedBy|pushed by"
+msgid "Project export started. A download link will be sent by email."
 msgstr ""
 
-msgid "From issue creation until deploy to production"
+msgid "Project home"
 msgstr ""
 
-msgid "From merge request merge until deploy to production"
+msgid "ProjectFeature|Disabled"
 msgstr ""
 
-msgid "Interval Pattern"
+msgid "ProjectFeature|Everyone with access"
 msgstr ""
 
-msgid "Introducing Cycle Analytics"
+msgid "ProjectFeature|Only team members"
 msgstr ""
 
-msgid "Last %d day"
-msgid_plural "Last %d days"
-msgstr[0] ""
-msgstr[1] ""
+msgid "ProjectFileTree|Name"
+msgstr ""
 
-msgid "Last Pipeline"
+msgid "ProjectLastActivity|Never"
 msgstr ""
 
-msgid "Limited to showing %d event at most"
-msgid_plural "Limited to showing %d events at most"
-msgstr[0] ""
-msgstr[1] ""
+msgid "ProjectLifecycle|Stage"
+msgstr ""
 
-msgid "Median"
+msgid "ProjectNetworkGraph|Graph"
 msgstr ""
 
-msgid "New Issue"
-msgid_plural "New Issues"
-msgstr[0] ""
-msgstr[1] ""
+msgid "Read more"
+msgstr ""
 
-msgid "New Pipeline Schedule"
+msgid "Readme"
 msgstr ""
 
-msgid "No schedules"
+msgid "RefSwitcher|Branches"
 msgstr ""
 
-msgid "Not available"
+msgid "RefSwitcher|Tags"
 msgstr ""
 
-msgid "Not enough data"
+msgid "Related Commits"
 msgstr ""
 
-msgid "OpenedNDaysAgo|Opened"
+msgid "Related Deployed Jobs"
 msgstr ""
 
-msgid "Owner"
+msgid "Related Issues"
 msgstr ""
 
-msgid "Pipeline Health"
+msgid "Related Jobs"
 msgstr ""
 
-msgid "Pipeline Schedule"
+msgid "Related Merge Requests"
 msgstr ""
 
-msgid "Pipeline Schedules"
+msgid "Related Merged Requests"
 msgstr ""
 
-msgid "PipelineSchedules|Activated"
+msgid "Remind later"
 msgstr ""
 
-msgid "PipelineSchedules|Active"
+msgid "Remove project"
 msgstr ""
 
-msgid "PipelineSchedules|All"
+msgid "Request Access"
 msgstr ""
 
-msgid "PipelineSchedules|Inactive"
+msgid "Revert this commit"
 msgstr ""
 
-msgid "PipelineSchedules|Next Run"
+msgid "Revert this merge request"
 msgstr ""
 
-msgid "PipelineSchedules|None"
+msgid "Save pipeline schedule"
 msgstr ""
 
-msgid "PipelineSchedules|Provide a short description for this pipeline"
+msgid "Schedule a new pipeline"
 msgstr ""
 
-msgid "PipelineSchedules|Take ownership"
+msgid "Scheduling Pipelines"
 msgstr ""
 
-msgid "PipelineSchedules|Target"
+msgid "Search branches and tags"
 msgstr ""
 
-msgid "ProjectLifecycle|Stage"
+msgid "Select Archive Format"
 msgstr ""
 
-msgid "Read more"
+msgid "Select a timezone"
 msgstr ""
 
-msgid "Related Commits"
+msgid "Select target branch"
 msgstr ""
 
-msgid "Related Deployed Jobs"
+msgid "Set a password on your account to pull or push via %{protocol}."
 msgstr ""
 
-msgid "Related Issues"
+msgid "Set up CI"
 msgstr ""
 
-msgid "Related Jobs"
+msgid "Set up Koding"
 msgstr ""
 
-msgid "Related Merge Requests"
+msgid "Set up auto deploy"
 msgstr ""
 
-msgid "Related Merged Requests"
+msgid "SetPasswordToCloneLink|set a password"
 msgstr ""
 
-msgid "Save pipeline schedule"
+msgid "Showing %d event"
+msgid_plural "Showing %d events"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Source code"
 msgstr ""
 
-msgid "Schedule a new pipeline"
+msgid "StarProject|Star"
 msgstr ""
 
-msgid "Select a timezone"
+msgid "Start a %{new_merge_request} with these changes"
 msgstr ""
 
-msgid "Select target branch"
+msgid "Switch branch/tag"
 msgstr ""
 
-msgid "Showing %d event"
-msgid_plural "Showing %d events"
+msgid "Tag"
+msgid_plural "Tags"
 msgstr[0] ""
 msgstr[1] ""
 
+msgid "Tags"
+msgstr ""
+
 msgid "Target Branch"
 msgstr ""
 
@@ -228,18 +822,33 @@ msgstr ""
 msgid "The collection of events added to the data gathered for that stage."
 msgstr ""
 
+msgid "The fork relationship has been removed."
+msgstr ""
+
 msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage."
 msgstr ""
 
 msgid "The phase of the development lifecycle."
 msgstr ""
 
+msgid "The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user."
+msgstr ""
+
 msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit."
 msgstr ""
 
 msgid "The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
 msgstr ""
 
+msgid "The project can be accessed by any logged in user."
+msgstr ""
+
+msgid "The project can be accessed without any authentication."
+msgstr ""
+
+msgid "The repository for this project does not exist."
+msgstr ""
+
 msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request."
 msgstr ""
 
@@ -255,6 +864,9 @@ msgstr ""
 msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
 msgstr ""
 
+msgid "This means you can not push code until you create an empty repository or import existing one."
+msgstr ""
+
 msgid "Time before an issue gets scheduled"
 msgstr ""
 
@@ -267,6 +879,129 @@ msgstr ""
 msgid "Time until first merge request"
 msgstr ""
 
+msgid "Timeago|%s days ago"
+msgstr ""
+
+msgid "Timeago|%s days remaining"
+msgstr ""
+
+msgid "Timeago|%s hours remaining"
+msgstr ""
+
+msgid "Timeago|%s minutes ago"
+msgstr ""
+
+msgid "Timeago|%s minutes remaining"
+msgstr ""
+
+msgid "Timeago|%s months ago"
+msgstr ""
+
+msgid "Timeago|%s months remaining"
+msgstr ""
+
+msgid "Timeago|%s seconds remaining"
+msgstr ""
+
+msgid "Timeago|%s weeks ago"
+msgstr ""
+
+msgid "Timeago|%s weeks remaining"
+msgstr ""
+
+msgid "Timeago|%s years ago"
+msgstr ""
+
+msgid "Timeago|%s years remaining"
+msgstr ""
+
+msgid "Timeago|1 day remaining"
+msgstr ""
+
+msgid "Timeago|1 hour remaining"
+msgstr ""
+
+msgid "Timeago|1 minute remaining"
+msgstr ""
+
+msgid "Timeago|1 month remaining"
+msgstr ""
+
+msgid "Timeago|1 week remaining"
+msgstr ""
+
+msgid "Timeago|1 year remaining"
+msgstr ""
+
+msgid "Timeago|Past due"
+msgstr ""
+
+msgid "Timeago|a day ago"
+msgstr ""
+
+msgid "Timeago|a month ago"
+msgstr ""
+
+msgid "Timeago|a week ago"
+msgstr ""
+
+msgid "Timeago|a while"
+msgstr ""
+
+msgid "Timeago|a year ago"
+msgstr ""
+
+msgid "Timeago|about %s hours ago"
+msgstr ""
+
+msgid "Timeago|about a minute ago"
+msgstr ""
+
+msgid "Timeago|about an hour ago"
+msgstr ""
+
+msgid "Timeago|in %s days"
+msgstr ""
+
+msgid "Timeago|in %s hours"
+msgstr ""
+
+msgid "Timeago|in %s minutes"
+msgstr ""
+
+msgid "Timeago|in %s months"
+msgstr ""
+
+msgid "Timeago|in %s seconds"
+msgstr ""
+
+msgid "Timeago|in %s weeks"
+msgstr ""
+
+msgid "Timeago|in %s years"
+msgstr ""
+
+msgid "Timeago|in 1 day"
+msgstr ""
+
+msgid "Timeago|in 1 hour"
+msgstr ""
+
+msgid "Timeago|in 1 minute"
+msgstr ""
+
+msgid "Timeago|in 1 month"
+msgstr ""
+
+msgid "Timeago|in 1 week"
+msgstr ""
+
+msgid "Timeago|in 1 year"
+msgstr ""
+
+msgid "Timeago|less than a minute ago"
+msgstr ""
+
 msgid "Time|hr"
 msgid_plural "Time|hrs"
 msgstr[0] ""
@@ -286,16 +1021,102 @@ msgstr ""
 msgid "Total test time for all commits/merges"
 msgstr ""
 
+msgid "Unstar"
+msgstr ""
+
+msgid "Upload New File"
+msgstr ""
+
+msgid "Upload file"
+msgstr ""
+
+msgid "UploadLink|click to upload"
+msgstr ""
+
+msgid "Use your global notification setting"
+msgstr ""
+
+msgid "View open merge request"
+msgstr ""
+
+msgid "VisibilityLevel|Internal"
+msgstr ""
+
+msgid "VisibilityLevel|Private"
+msgstr ""
+
+msgid "VisibilityLevel|Public"
+msgstr ""
+
 msgid "Want to see the data? Please ask an administrator for access."
 msgstr ""
 
 msgid "We don't have enough data to show this stage."
 msgstr ""
 
+msgid "Withdraw Access Request"
+msgstr ""
+
+msgid ""
+"You are going to remove %{project_name_with_namespace}.\n"
+"Removed project CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You can only add files when you are on a branch"
+msgstr ""
+
+msgid "You have reached your project limit"
+msgstr ""
+
+msgid "You must sign in to star a project"
+msgstr ""
+
 msgid "You need permission."
 msgstr ""
 
+msgid "You will not get any notifications via email"
+msgstr ""
+
+msgid "You will only receive notifications for the events you choose"
+msgstr ""
+
+msgid "You will only receive notifications for threads you have participated in"
+msgstr ""
+
+msgid "You will receive notifications for any activity"
+msgstr ""
+
+msgid "You will receive notifications only for comments in which you were @mentioned"
+msgstr ""
+
+msgid "You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account"
+msgstr ""
+
+msgid "You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile"
+msgstr ""
+
+msgid "Your name"
+msgstr ""
+
 msgid "day"
 msgid_plural "days"
 msgstr[0] ""
 msgstr[1] ""
+
+msgid "new merge request"
+msgstr ""
+
+msgid "notification emails"
+msgstr ""
+
+msgid "parent"
+msgid_plural "parents"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/locale/it/gitlab.po b/locale/it/gitlab.po
new file mode 100644
index 0000000000000000000000000000000000000000..db99202140307277b3978fa1d072b475253eb3b6
--- /dev/null
+++ b/locale/it/gitlab.po
@@ -0,0 +1,1185 @@
+# Huang Tao <htve@outlook.com>, 2017. #zanata
+# Paolo Falomo <info@paolofalomo.it>, 2017. #zanata
+msgid ""
+msgstr ""
+"Project-Id-Version: gitlab 1.0.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-06-19 15:50-0500\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"PO-Revision-Date: 2017-07-02 10:32-0400\n"
+"Last-Translator: Paolo Falomo <info@paolofalomo.it>\n"
+"Language-Team: Italian\n"
+"Language: it\n"
+"X-Generator: Zanata 3.9.6\n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+
+msgid "%d additional commit has been omitted to prevent performance issues."
+msgid_plural ""
+"%d additional commits have been omitted to prevent performance issues."
+msgstr[0] ""
+"%d commit aggiuntivo è stato omesso per evitare degradi di prestazioni negli "
+"issues."
+msgstr[1] ""
+"%d commit aggiuntivi sono stati omessi per evitare degradi di prestazioni "
+"negli issues."
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] "%d commit"
+msgstr[1] "%d commit"
+
+msgid "%{commit_author_link} committed %{commit_timeago}"
+msgstr "%{commit_author_link} ha committato %{commit_timeago}"
+
+msgid "About auto deploy"
+msgstr "Riguardo il rilascio automatico"
+
+msgid "Active"
+msgstr "Attivo"
+
+msgid "Activity"
+msgstr "Attività"
+
+msgid "Add Changelog"
+msgstr "Aggiungi Changelog"
+
+msgid "Add Contribution guide"
+msgstr "Aggiungi Guida per contribuire"
+
+msgid "Add License"
+msgstr "Aggiungi Licenza"
+
+msgid "Add an SSH key to your profile to pull or push via SSH."
+msgstr ""
+"Aggiungi una chiave SSH al tuo profilo per eseguire pull o push tramite SSH"
+
+msgid "Add new directory"
+msgstr "Aggiungi una directory (cartella)"
+
+msgid "Archived project! Repository is read-only"
+msgstr "Progetto archiviato! La Repository è sola-lettura"
+
+msgid "Are you sure you want to delete this pipeline schedule?"
+msgstr "Sei sicuro di voler cancellare questa pipeline programmata?"
+
+msgid "Attach a file by drag &amp; drop or %{upload_link}"
+msgstr ""
+"Aggiungi un file tramite trascina &amp; rilascia ( drag &amp; drop) o "
+"%{upload_link}"
+
+msgid "Branch"
+msgid_plural "Branches"
+msgstr[0] "Branch"
+msgstr[1] "Branches"
+
+msgid ""
+"Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, "
+"choose a GitLab CI Yaml template and commit your changes. "
+"%{link_to_autodeploy_doc}"
+msgstr ""
+"La branch <strong>%{branch_name}</strong> è stata creata. Per impostare un "
+"rilascio automatico scegli un template CI di Gitlab e committa le tue "
+"modifiche %{link_to_autodeploy_doc}"
+
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr "Cerca branches"
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr "Cambia branch"
+
+msgid "Branches"
+msgstr "Branches"
+
+msgid "Browse Directory"
+msgstr "Naviga direttori"
+
+msgid "Browse File"
+msgstr "Esplora File"
+
+msgid "Browse Files"
+msgstr "Esplora Files"
+
+msgid "Browse files"
+msgstr "Guarda i files"
+
+msgid "ByAuthor|by"
+msgstr "per"
+
+msgid "CI configuration"
+msgstr "Configurazione CI (Integrazione Continua)"
+
+msgid "Cancel"
+msgstr "Cancella"
+
+msgid "ChangeTypeActionLabel|Pick into branch"
+msgstr "Preleva nella branch"
+
+msgid "ChangeTypeActionLabel|Revert in branch"
+msgstr "Ripristina nella branch"
+
+msgid "ChangeTypeAction|Cherry-pick"
+msgstr "Cherry-pick"
+
+msgid "ChangeTypeAction|Revert"
+msgstr "Ripristina"
+
+msgid "Changelog"
+msgstr "Changelog"
+
+msgid "Charts"
+msgstr "Grafici"
+
+msgid "Cherry-pick this commit"
+msgstr "Cherry-pick this commit"
+
+msgid "Cherry-pick this merge request"
+msgstr "Cherry-pick questa richiesta di merge"
+
+msgid "CiStatusLabel|canceled"
+msgstr "cancellato"
+
+msgid "CiStatusLabel|created"
+msgstr "creato"
+
+msgid "CiStatusLabel|failed"
+msgstr "fallito"
+
+msgid "CiStatusLabel|manual action"
+msgstr "azione manuale"
+
+msgid "CiStatusLabel|passed"
+msgstr "superata"
+
+msgid "CiStatusLabel|passed with warnings"
+msgstr "superata con avvisi"
+
+msgid "CiStatusLabel|pending"
+msgstr "in coda"
+
+msgid "CiStatusLabel|skipped"
+msgstr "saltata"
+
+msgid "CiStatusLabel|waiting for manual action"
+msgstr "in attesa di azione manuale"
+
+msgid "CiStatusText|blocked"
+msgstr "bloccata"
+
+msgid "CiStatusText|canceled"
+msgstr "cancellata"
+
+msgid "CiStatusText|created"
+msgstr "creata"
+
+msgid "CiStatusText|failed"
+msgstr "fallita"
+
+msgid "CiStatusText|manual"
+msgstr "manuale"
+
+msgid "CiStatusText|passed"
+msgstr "superata"
+
+msgid "CiStatusText|pending"
+msgstr "in coda"
+
+msgid "CiStatusText|skipped"
+msgstr "saltata"
+
+msgid "CiStatus|running"
+msgstr "in corso"
+
+msgid "Commit"
+msgid_plural "Commits"
+msgstr[0] "Commit"
+msgstr[1] "Commits"
+
+msgid "Commit message"
+msgstr "Messaggio del commit"
+
+msgid "CommitBoxTitle|Commit"
+msgstr "Commit"
+
+msgid "CommitMessage|Add %{file_name}"
+msgstr "Aggiungi %{file_name}"
+
+msgid "Commits"
+msgstr "Commits"
+
+msgid "Commits feed"
+msgstr "Feed dei Commits"
+
+msgid "Commits|History"
+msgstr "Cronologia"
+
+msgid "Committed by"
+msgstr "Committato da "
+
+msgid "Compare"
+msgstr "Confronta"
+
+msgid "Contribution guide"
+msgstr "Guida per contribuire"
+
+msgid "Contributors"
+msgstr "Collaboratori"
+
+msgid "Copy URL to clipboard"
+msgstr "Copia URL negli appunti"
+
+msgid "Copy commit SHA to clipboard"
+msgstr "Copia l'SHA del commit negli appunti"
+
+msgid "Create New Directory"
+msgstr "Crea una nuova cartella"
+
+msgid "Create directory"
+msgstr "Crea cartella"
+
+msgid "Create empty bare repository"
+msgstr "Crea una repository vuota"
+
+msgid "Create merge request"
+msgstr "Crea una richiesta di merge"
+
+msgid "Create new..."
+msgstr "Crea nuovo..."
+
+msgid "CreateNewFork|Fork"
+msgstr "Fork"
+
+msgid "CreateTag|Tag"
+msgstr "Tag"
+
+msgid "Cron Timezone"
+msgstr "Cron Timezone"
+
+msgid "Cron syntax"
+msgstr "Sintassi Cron"
+
+msgid "Custom notification events"
+msgstr "Eventi-Notifica personalizzati"
+
+msgid ""
+"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 %{notification_link}."
+msgstr ""
+"I livelli di notifica personalizzati sono uguali a quelli di partecipazione. "
+"Con i livelli di notifica personalizzati riceverai anche notifiche per gli "
+"eventi da te scelti %{notification_link}."
+
+msgid "Cycle Analytics"
+msgstr "Statistiche Cicliche"
+
+msgid ""
+"Cycle Analytics gives an overview of how much time it takes to go from idea "
+"to production in your project."
+msgstr ""
+"L'Analisi Ciclica fornisce una panoramica sul tempo che trascorre tra l'idea "
+"ed il rilascio in produzione del tuo progetto"
+
+msgid "CycleAnalyticsStage|Code"
+msgstr "Codice"
+
+msgid "CycleAnalyticsStage|Issue"
+msgstr "Issue"
+
+msgid "CycleAnalyticsStage|Plan"
+msgstr "Pianificazione"
+
+msgid "CycleAnalyticsStage|Production"
+msgstr "Produzione"
+
+msgid "CycleAnalyticsStage|Review"
+msgstr "Revisione"
+
+msgid "CycleAnalyticsStage|Staging"
+msgstr "Pre-rilascio"
+
+msgid "CycleAnalyticsStage|Test"
+msgstr "Test"
+
+msgid "Define a custom pattern with cron syntax"
+msgstr "Definisci un patter personalizzato mediante la sintassi cron"
+
+msgid "Delete"
+msgstr "Elimina"
+
+msgid "Deploy"
+msgid_plural "Deploys"
+msgstr[0] "Rilascio"
+msgstr[1] "Rilasci"
+
+msgid "Description"
+msgstr "Descrizione"
+
+msgid "Directory name"
+msgstr "Nome cartella"
+
+msgid "Don't show again"
+msgstr "Non mostrare più"
+
+msgid "Download"
+msgstr "Scarica"
+
+msgid "Download tar"
+msgstr "Scarica tar"
+
+msgid "Download tar.bz2"
+msgstr "Scarica tar.bz2"
+
+msgid "Download tar.gz"
+msgstr "Scarica tar.gz"
+
+msgid "Download zip"
+msgstr "Scarica zip"
+
+msgid "DownloadArtifacts|Download"
+msgstr "Scarica"
+
+msgid "DownloadCommit|Email Patches"
+msgstr "Email Patches"
+
+msgid "DownloadCommit|Plain Diff"
+msgstr "Differenze"
+
+msgid "DownloadSource|Download"
+msgstr "Scarica"
+
+msgid "Edit"
+msgstr "Modifica"
+
+msgid "Edit Pipeline Schedule %{id}"
+msgstr "Cambia programmazione della pipeline %{id}"
+
+msgid "Every day (at 4:00am)"
+msgstr "Ogni giorno (alle 4 del mattino)"
+
+msgid "Every month (on the 1st at 4:00am)"
+msgstr "Ogni primo giorno del mese (alle 4 del mattino)"
+
+msgid "Every week (Sundays at 4:00am)"
+msgstr "Ogni settimana (Di domenica alle 4 del mattino)"
+
+msgid "Failed to change the owner"
+msgstr "Impossibile cambiare owner"
+
+msgid "Failed to remove the pipeline schedule"
+msgstr "Impossibile rimuovere la pipeline pianificata"
+
+msgid "Files"
+msgstr "Files"
+
+msgid "Filter by commit message"
+msgstr "Filtra per messaggio di commit"
+
+msgid "Find by path"
+msgstr "Trova in percorso"
+
+msgid "Find file"
+msgstr "Trova file"
+
+msgid "FirstPushedBy|First"
+msgstr "Primo"
+
+msgid "FirstPushedBy|pushed by"
+msgstr "Push di"
+
+msgid "Fork"
+msgid_plural "Forks"
+msgstr[0] "Fork"
+msgstr[1] "Forks"
+
+msgid "ForkedFromProjectPath|Forked from"
+msgstr "Fork da"
+
+msgid "From issue creation until deploy to production"
+msgstr "Dalla creazione di un issue fino al rilascio in produzione"
+
+msgid "From merge request merge until deploy to production"
+msgstr ""
+"Dalla richiesta di merge fino effettua il merge fino al rilascio in "
+"produzione"
+
+msgid "Go to your fork"
+msgstr "Vai il tuo fork"
+
+msgid "GoToYourFork|Fork"
+msgstr "Fork"
+
+msgid "Home"
+msgstr "Home"
+
+msgid "Housekeeping successfully started"
+msgstr "Housekeeping iniziato con successo"
+
+msgid "Import repository"
+msgstr "Importa repository"
+
+msgid "Interval Pattern"
+msgstr "Intervallo di Pattern"
+
+msgid "Introducing Cycle Analytics"
+msgstr "Introduzione delle Analisi Cicliche"
+
+msgid "LFSStatus|Disabled"
+msgstr "Disabilitato"
+
+msgid "LFSStatus|Enabled"
+msgstr "Abilitato"
+
+msgid "Last %d day"
+msgid_plural "Last %d days"
+msgstr[0] "L'ultimo %d giorno"
+msgstr[1] "Gli ultimi %d giorni"
+
+msgid "Last Pipeline"
+msgstr "Ultima Pipeline"
+
+msgid "Last Update"
+msgstr "Ultimo Aggiornamento"
+
+msgid "Last commit"
+msgstr "Ultimo Commit"
+
+msgid "Learn more in the"
+msgstr "Leggi di più su"
+
+msgid "Learn more in the|pipeline schedules documentation"
+msgstr "documentazione sulla pianificazione delle pipelines"
+
+msgid "Leave group"
+msgstr "Abbandona il gruppo"
+
+msgid "Leave project"
+msgstr "Abbandona il progetto"
+
+msgid "Limited to showing %d event at most"
+msgid_plural "Limited to showing %d events at most"
+msgstr[0] "Limita visualizzazione %d d'evento"
+msgstr[1] "Limita visualizzazione %d di eventi"
+
+msgid "Median"
+msgstr "Mediano"
+
+msgid "MissingSSHKeyWarningLink|add an SSH key"
+msgstr "aggiungi una chiave SSH"
+
+msgid "New Issue"
+msgid_plural "New Issues"
+msgstr[0] "Nuovo Issue"
+msgstr[1] "Nuovi Issues"
+
+msgid "New Pipeline Schedule"
+msgstr "Nuova pianificazione Pipeline"
+
+msgid "New branch"
+msgstr "Nuova Branch"
+
+msgid "New directory"
+msgstr "Nuova directory"
+
+msgid "New file"
+msgstr "Nuovo file"
+
+msgid "New issue"
+msgstr "Nuovo Issue"
+
+msgid "New merge request"
+msgstr "Nuova richiesta di merge"
+
+msgid "New schedule"
+msgstr "Nuova pianficazione"
+
+msgid "New snippet"
+msgstr "Nuovo snippet"
+
+msgid "New tag"
+msgstr "Nuovo tag"
+
+msgid "No repository"
+msgstr "Nessuna Repository"
+
+msgid "No schedules"
+msgstr "Nessuna pianificazione"
+
+msgid "Not available"
+msgstr "Non disponibile"
+
+msgid "Not enough data"
+msgstr "Dati insufficienti "
+
+msgid "Notification events"
+msgstr "Notifica eventi"
+
+msgid "NotificationEvent|Close issue"
+msgstr "Chiudi issue"
+
+msgid "NotificationEvent|Close merge request"
+msgstr "Chiudi richiesta di merge"
+
+msgid "NotificationEvent|Failed pipeline"
+msgstr "Pipeline fallita"
+
+msgid "NotificationEvent|Merge merge request"
+msgstr "Completa la richiesta di merge"
+
+msgid "NotificationEvent|New issue"
+msgstr "Nuovo issue"
+
+msgid "NotificationEvent|New merge request"
+msgstr "Nuova richiesta di merge"
+
+msgid "NotificationEvent|New note"
+msgstr "Nuova nota"
+
+msgid "NotificationEvent|Reassign issue"
+msgstr "Riassegna issue"
+
+msgid "NotificationEvent|Reassign merge request"
+msgstr "Riassegna richiesta di Merge"
+
+msgid "NotificationEvent|Reopen issue"
+msgstr "Riapri issue"
+
+msgid "NotificationEvent|Successful pipeline"
+msgstr "Pipeline Completata"
+
+msgid "NotificationLevel|Custom"
+msgstr "Personalizzato"
+
+msgid "NotificationLevel|Disabled"
+msgstr "Disabilitato"
+
+msgid "NotificationLevel|Global"
+msgstr "Globale"
+
+msgid "NotificationLevel|On mention"
+msgstr "Se menzionato"
+
+msgid "NotificationLevel|Participate"
+msgstr "Partecipa"
+
+msgid "NotificationLevel|Watch"
+msgstr "Osserva"
+
+msgid "OfSearchInADropdown|Filter"
+msgstr "Filtra"
+
+msgid "OpenedNDaysAgo|Opened"
+msgstr "Aperto"
+
+msgid "Options"
+msgstr "Opzioni"
+
+msgid "Owner"
+msgstr "Owner"
+
+msgid "Pipeline"
+msgstr "Pipeline"
+
+msgid "Pipeline Health"
+msgstr "Stato della Pipeline"
+
+msgid "Pipeline Schedule"
+msgstr "Pianificazione Pipeline"
+
+msgid "Pipeline Schedules"
+msgstr "Pianificazione multipla Pipeline"
+
+msgid "PipelineSchedules|Activated"
+msgstr "Attivata"
+
+msgid "PipelineSchedules|Active"
+msgstr "Attiva"
+
+msgid "PipelineSchedules|All"
+msgstr "Tutto"
+
+msgid "PipelineSchedules|Inactive"
+msgstr "Inattiva"
+
+msgid "PipelineSchedules|Next Run"
+msgstr "Prossima esecuzione"
+
+msgid "PipelineSchedules|None"
+msgstr "Nessuna"
+
+msgid "PipelineSchedules|Provide a short description for this pipeline"
+msgstr "Fornisci una breve descrizione per questa pipeline"
+
+msgid "PipelineSchedules|Take ownership"
+msgstr "Prendi possesso"
+
+msgid "PipelineSchedules|Target"
+msgstr "Target"
+
+msgid "PipelineSheduleIntervalPattern|Custom"
+msgstr "Personalizzato"
+
+msgid "Pipeline|with stage"
+msgstr "con stadio"
+
+msgid "Pipeline|with stages"
+msgstr "con più stadi"
+
+msgid "Project '%{project_name}' queued for deletion."
+msgstr "Il Progetto '%{project_name}' in coda di eliminazione."
+
+msgid "Project '%{project_name}' was successfully created."
+msgstr "Il Progetto '%{project_name}' è stato creato con successo."
+
+msgid "Project '%{project_name}' was successfully updated."
+msgstr "Il Progetto '%{project_name}' è stato aggiornato con successo."
+
+msgid "Project '%{project_name}' will be deleted."
+msgstr "Il Progetto '%{project_name}' verrà eliminato"
+
+msgid "Project access must be granted explicitly to each user."
+msgstr "L'accesso al progetto dev'esser fornito esplicitamente ad ogni utente"
+
+msgid "Project export could not be deleted."
+msgstr "L'esportazione del progetto non può essere eliminata."
+
+msgid "Project export has been deleted."
+msgstr "L'esportazione del progetto è stata eliminata."
+
+msgid ""
+"Project export link has expired. Please generate a new export from your "
+"project settings."
+msgstr ""
+"Il link d'esportazione del progetto è scaduto. Genera una nuova esportazione "
+"dalle impostazioni del tuo progetto."
+
+msgid "Project export started. A download link will be sent by email."
+msgstr ""
+"Esportazione del progetto iniziata. Un link di download sarà inviato via "
+"email."
+
+msgid "Project home"
+msgstr "Home di progetto"
+
+msgid "ProjectFeature|Disabled"
+msgstr "Disabilitato"
+
+msgid "ProjectFeature|Everyone with access"
+msgstr "Chiunque con accesso"
+
+msgid "ProjectFeature|Only team members"
+msgstr "Solo i membri del team"
+
+msgid "ProjectFileTree|Name"
+msgstr "Nome"
+
+msgid "ProjectLastActivity|Never"
+msgstr "Mai"
+
+msgid "ProjectLifecycle|Stage"
+msgstr "Stadio"
+
+msgid "ProjectNetworkGraph|Graph"
+msgstr "Grafico"
+
+msgid "Read more"
+msgstr "Continua..."
+
+msgid "Readme"
+msgstr "Leggimi"
+
+msgid "RefSwitcher|Branches"
+msgstr "Branches"
+
+msgid "RefSwitcher|Tags"
+msgstr "Tags"
+
+msgid "Related Commits"
+msgstr "Commit correlati"
+
+msgid "Related Deployed Jobs"
+msgstr "Attività di Rilascio Correlate"
+
+msgid "Related Issues"
+msgstr "Issues Correlati"
+
+msgid "Related Jobs"
+msgstr "Attività Correlate"
+
+msgid "Related Merge Requests"
+msgstr "Richieste di Merge Correlate"
+
+msgid "Related Merged Requests"
+msgstr "Richieste di Merge Completate Correlate"
+
+msgid "Remind later"
+msgstr "Ricordamelo più tardi"
+
+msgid "Remove project"
+msgstr "Rimuovi progetto"
+
+msgid "Request Access"
+msgstr "Richiedi accesso"
+
+msgid "Revert this commit"
+msgstr "Ripristina questo commit"
+
+msgid "Revert this merge request"
+msgstr "Ripristina questa richiesta di merge"
+
+msgid "Save pipeline schedule"
+msgstr "Salva pianificazione pipeline"
+
+msgid "Schedule a new pipeline"
+msgstr "Pianifica una nuova Pipeline"
+
+msgid "Scheduling Pipelines"
+msgstr "Pianificazione pipelines"
+
+msgid "Search branches and tags"
+msgstr "Ricerca branches e tags"
+
+msgid "Select Archive Format"
+msgstr "Seleziona formato d'archivio"
+
+msgid "Select a timezone"
+msgstr "Seleziona una timezone"
+
+msgid "Select target branch"
+msgstr "Seleziona una branch di destinazione"
+
+msgid "Set a password on your account to pull or push via %{protocol}."
+msgstr ""
+"Imposta una password sul tuo account per eseguire pull o push tramite "
+"%{protocol}."
+
+msgid "Set up CI"
+msgstr "Configura CI"
+
+msgid "Set up Koding"
+msgstr "Configura Koding"
+
+msgid "Set up auto deploy"
+msgstr "Configura il rilascio automatico"
+
+msgid "SetPasswordToCloneLink|set a password"
+msgstr "imposta una password"
+
+msgid "Showing %d event"
+msgid_plural "Showing %d events"
+msgstr[0] "Visualizza %d evento"
+msgstr[1] "Visualizza %d eventi"
+
+msgid "Source code"
+msgstr "Codice Sorgente"
+
+msgid "StarProject|Star"
+msgstr "Star"
+
+msgid "Start a %{new_merge_request} with these changes"
+msgstr "inizia una %{new_merge_request} con queste modifiche"
+
+msgid "Switch branch/tag"
+msgstr "Cambia branch/tag"
+
+msgid "Tag"
+msgid_plural "Tags"
+msgstr[0] "Tag"
+msgstr[1] "Tags"
+
+msgid "Tags"
+msgstr "Tags"
+
+msgid "Target Branch"
+msgstr "Branch di destinazione"
+
+msgid ""
+"The coding stage shows the time from the first commit to creating the merge "
+"request. The data will automatically be added here once you create your "
+"first merge request."
+msgstr ""
+"Lo stadio di programmazione mostra il tempo trascorso dal primo commit alla "
+"creazione di una richiesta di merge (MR). I dati saranno aggiunti una volta "
+"che avrai creato la prima richiesta di merge."
+
+msgid "The collection of events added to the data gathered for that stage."
+msgstr "L'insieme di eventi aggiunti ai dati raccolti per quello stadio."
+
+msgid "The fork relationship has been removed."
+msgstr "La relazione del fork è stata rimossa"
+
+msgid ""
+"The issue stage shows the time it takes from creating an issue to assigning "
+"the issue to a milestone, or add the issue to a list on your Issue Board. "
+"Begin creating issues to see data for this stage."
+msgstr ""
+"Questo stadio di issue mostra il tempo che ci vuole dal creare un issue "
+"all'assegnarli una milestone, o ad aggiungere un issue alla tua board. Crea "
+"un issue per vedere questo stadio."
+
+msgid "The phase of the development lifecycle."
+msgstr "Il ciclo vitale della fase di sviluppo."
+
+msgid ""
+"The pipelines schedule runs pipelines in the future, repeatedly, for "
+"specific branches or tags. Those scheduled pipelines will inherit limited "
+"project access based on their associated user."
+msgstr ""
+"Le pipelines pianificate vengono eseguite nel futuro, ripetitivamente, per "
+"specifici tag o branch ed ereditano restrizioni di progetto basate "
+"sull'utente ad esse associato."
+
+msgid ""
+"The planning stage shows the time from the previous step to pushing your "
+"first commit. This time will be added automatically once you push your first "
+"commit."
+msgstr ""
+"Lo stadio di pianificazione mostra il tempo trascorso dal primo commit al "
+"suo step precedente. Questo periodo sarà disponibile automaticamente nel "
+"momento in cui farai il primo commit."
+
+msgid ""
+"The production stage shows the total time it takes between creating an issue "
+"and deploying the code to production. The data will be automatically added "
+"once you have completed the full idea to production cycle."
+msgstr ""
+"Lo stadio di produzione mostra il tempo totale che trascorre tra la "
+"creazione di un issue il suo rilascio (inteso come codice) in produzione.  "
+"Questo dato sarà disponibile automaticamente nel momento in cui avrai "
+"completato l'intero processo ideale del ciclo di produzione"
+
+msgid "The project can be accessed by any logged in user."
+msgstr "Qualunque utente autenticato può accedere a questo progetto."
+
+msgid "The project can be accessed without any authentication."
+msgstr ""
+"Chiunque può accedere a questo progetto (senza alcuna autenticazione)."
+
+msgid "The repository for this project does not exist."
+msgstr "La repository di questo progetto non esiste."
+
+msgid ""
+"The review stage shows the time from creating the merge request to merging "
+"it. The data will automatically be added after you merge your first merge "
+"request."
+msgstr ""
+"Lo stadio di revisione mostra il tempo tra una richiesta di merge al suo "
+"svolgimento effettivo. Questo dato sarà disponibile appena avrai completato "
+"una MR (Merger Request)"
+
+msgid ""
+"The staging stage shows the time between merging the MR and deploying code "
+"to the production environment. The data will be automatically added once you "
+"deploy to production for the first time."
+msgstr ""
+"Lo stadio di pre-rilascio mostra il tempo che trascorre da una MR (Richiesta "
+"di Merge) completata al suo rilascio in ambiente di produzione. Questa "
+"informazione sarà disponibile dal tuo primo rilascio in produzione"
+
+msgid ""
+"The testing stage shows the time GitLab CI takes to run every pipeline for "
+"the related merge request. The data will automatically be added after your "
+"first pipeline finishes running."
+msgstr ""
+"Lo stadio di test mostra il tempo che ogni Pipeline impiega per essere "
+"eseguita in ogni Richiesta di Merge correlata. L'informazione sarà "
+"disponibile automaticamente quando la tua prima Pipeline avrà finito d'esser "
+"eseguita."
+
+msgid "The time taken by each data entry gathered by that stage."
+msgstr ""
+"Il tempo aggregato relativo eventi/data entry raccolto in quello stadio."
+
+msgid ""
+"The value lying at the midpoint of a series of observed values. E.g., "
+"between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 ="
+" 6."
+msgstr ""
+"Il valore falsato nel mezzo di una serie di dati osservati. ES: tra 3,5,9 il "
+"mediano è 5. Tra 3,5,7,8 il mediano è (5+7)/2  quindi 6."
+
+msgid ""
+"This means you can not push code until you create an empty repository or "
+"import existing one."
+msgstr ""
+"Questo significa che non è possibile effettuare push di codice fino a che "
+"non crei una repository vuota o ne importi una esistente"
+
+msgid "Time before an issue gets scheduled"
+msgstr "Il tempo che impiega un issue per esser pianificato"
+
+msgid "Time before an issue starts implementation"
+msgstr "Il tempo che impiega un issue per esser implementato"
+
+msgid "Time between merge request creation and merge/close"
+msgstr "Il tempo tra la creazione di una richiesta di merge ed il merge/close"
+
+msgid "Time until first merge request"
+msgstr "Il tempo fino alla prima richiesta di merge"
+
+msgid "Timeago|%s days ago"
+msgstr "%s giorni fa"
+
+msgid "Timeago|%s days remaining"
+msgstr "%s giorni rimanenti"
+
+msgid "Timeago|%s hours remaining"
+msgstr "%s ore rimanenti"
+
+msgid "Timeago|%s minutes ago"
+msgstr "%s minuti fa"
+
+msgid "Timeago|%s minutes remaining"
+msgstr "%s minuti rimanenti"
+
+msgid "Timeago|%s months ago"
+msgstr "%s minuti fa"
+
+msgid "Timeago|%s months remaining"
+msgstr "%s mesi rimanenti"
+
+msgid "Timeago|%s seconds remaining"
+msgstr "%s secondi rimanenti"
+
+msgid "Timeago|%s weeks ago"
+msgstr "%s settimane fa"
+
+msgid "Timeago|%s weeks remaining"
+msgstr "%s settimane rimanenti"
+
+msgid "Timeago|%s years ago"
+msgstr "%s anni fa"
+
+msgid "Timeago|%s years remaining"
+msgstr "%s anni rimanenti"
+
+msgid "Timeago|1 day remaining"
+msgstr "1 giorno rimanente"
+
+msgid "Timeago|1 hour remaining"
+msgstr "1 ora rimanente"
+
+msgid "Timeago|1 minute remaining"
+msgstr "1 minuto rimanente"
+
+msgid "Timeago|1 month remaining"
+msgstr "1 mese rimanente"
+
+msgid "Timeago|1 week remaining"
+msgstr "1 settimana rimanente"
+
+msgid "Timeago|1 year remaining"
+msgstr "1 anno rimanente"
+
+msgid "Timeago|Past due"
+msgstr "Entro"
+
+msgid "Timeago|a day ago"
+msgstr "un giorno fa"
+
+msgid "Timeago|a month ago"
+msgstr "un mese fa"
+
+msgid "Timeago|a week ago"
+msgstr "una settimana fa"
+
+msgid "Timeago|a while"
+msgstr "poco fa"
+
+msgid "Timeago|a year ago"
+msgstr "un anno fa"
+
+msgid "Timeago|about %s hours ago"
+msgstr "circa %s ore fa"
+
+msgid "Timeago|about a minute ago"
+msgstr "circa un minuto fa"
+
+msgid "Timeago|about an hour ago"
+msgstr "circa un ora fa"
+
+msgid "Timeago|in %s days"
+msgstr "in %s giorni"
+
+msgid "Timeago|in %s hours"
+msgstr "in %s ore"
+
+msgid "Timeago|in %s minutes"
+msgstr "in %s minuti"
+
+msgid "Timeago|in %s months"
+msgstr "in %s mesi"
+
+msgid "Timeago|in %s seconds"
+msgstr "in %s secondi"
+
+msgid "Timeago|in %s weeks"
+msgstr "in %s settimane"
+
+msgid "Timeago|in %s years"
+msgstr "in %s anni"
+
+msgid "Timeago|in 1 day"
+msgstr "in 1 giorno"
+
+msgid "Timeago|in 1 hour"
+msgstr "in 1 ora"
+
+msgid "Timeago|in 1 minute"
+msgstr "in 1 minuto"
+
+msgid "Timeago|in 1 month"
+msgstr "in 1 mese"
+
+msgid "Timeago|in 1 week"
+msgstr "in 1 settimana"
+
+msgid "Timeago|in 1 year"
+msgstr "in 1 anno"
+
+msgid "Timeago|less than a minute ago"
+msgstr "meno di un minuto fa"
+
+msgid "Time|hr"
+msgid_plural "Time|hrs"
+msgstr[0] "hr"
+msgstr[1] "hr"
+
+msgid "Time|min"
+msgid_plural "Time|mins"
+msgstr[0] "min"
+msgstr[1] "mins"
+
+msgid "Time|s"
+msgstr "s"
+
+msgid "Total Time"
+msgstr "Tempo Totale"
+
+msgid "Total test time for all commits/merges"
+msgstr "Tempo totale di test per tutti i commits/merges"
+
+msgid "Unstar"
+msgstr "Unstar"
+
+msgid "Upload New File"
+msgstr "Carica un nuovo file"
+
+msgid "Upload file"
+msgstr "Carica file"
+
+msgid "UploadLink|click to upload"
+msgstr "clicca per caricare"
+
+msgid "Use your global notification setting"
+msgstr "Usa le tue impostazioni globali "
+
+msgid "View open merge request"
+msgstr "Mostra la richieste di merge aperte"
+
+msgid "VisibilityLevel|Internal"
+msgstr "Interno"
+
+msgid "VisibilityLevel|Private"
+msgstr "Privato"
+
+msgid "VisibilityLevel|Public"
+msgstr "Pubblico"
+
+msgid "Want to see the data? Please ask an administrator for access."
+msgstr ""
+"Vuoi visualizzare i dati? Richiedi l'accesso ad un amministratore, grazie."
+
+msgid "We don't have enough data to show this stage."
+msgstr "Non ci sono sufficienti dati da mostrare su questo stadio"
+
+msgid "Withdraw Access Request"
+msgstr "Ritira richiesta d'accesso"
+
+msgid ""
+"You are going to remove %{project_name_with_namespace}.\n"
+"Removed project CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr ""
+"Stai per rimuovere %{project_name_with_namespace}.\n"
+"I progetti rimossi NON POSSONO essere ripristinati\n"
+"Sei assolutamente sicuro?"
+
+msgid ""
+"You are going to remove the fork relationship to source project "
+"%{forked_from_project}. Are you ABSOLUTELY sure?"
+msgstr ""
+"Stai per rimuovere la relazione con il progetto sorgente "
+"%{forked_from_project}. Sei ASSOLUTAMENTE sicuro?"
+
+msgid ""
+"You are going to transfer %{project_name_with_namespace} to another owner. "
+"Are you ABSOLUTELY sure?"
+msgstr ""
+"Stai per trasferire %{project_name_with_namespace} ad un altro owner. Sei "
+"ASSOLUTAMENTE sicuro?"
+
+msgid "You can only add files when you are on a branch"
+msgstr "Puoi aggiungere files solo quando sei in una branch"
+
+msgid "You have reached your project limit"
+msgstr "Hai raggiunto il tuo limite di progetto"
+
+msgid "You must sign in to star a project"
+msgstr "Devi accedere per porre una star al progetto"
+
+msgid "You need permission."
+msgstr "Necessiti del permesso."
+
+msgid "You will not get any notifications via email"
+msgstr "Non riceverai alcuna notifica via email"
+
+msgid "You will only receive notifications for the events you choose"
+msgstr "Riceverai notifiche solo per gli eventi che hai scelto"
+
+msgid ""
+"You will only receive notifications for threads you have participated in"
+msgstr "Riceverai notifiche solo per i threads a cui hai partecipato"
+
+msgid "You will receive notifications for any activity"
+msgstr "Riceverai notifiche per ogni attività"
+
+msgid ""
+"You will receive notifications only for comments in which you were "
+"@mentioned"
+msgstr "Riceverai notifiche solo per i commenti ai quale sei stato menzionato"
+
+msgid ""
+"You won't be able to pull or push project code via %{protocol} until you "
+"%{set_password_link} on your account"
+msgstr ""
+"Non sarai in grado di eseguire pull o push di codice tramite %{protocol} "
+"fino a che %{set_password_link} nel tuo account."
+
+msgid ""
+"You won't be able to pull or push project code via SSH until you "
+"%{add_ssh_key_link} to your profile"
+msgstr ""
+"Non sarai in grado di effettuare push o pull tramite SSH fino a che "
+"%{add_ssh_key_link} al tuo profilo"
+
+msgid "Your name"
+msgstr "Il tuo nome"
+
+msgid "day"
+msgid_plural "days"
+msgstr[0] "giorno"
+msgstr[1] "giorni"
+
+msgid "new merge request"
+msgstr "Nuova richiesta di merge"
+
+msgid "notification emails"
+msgstr "Notifiche via email"
+
+msgid "parent"
+msgid_plural "parents"
+msgstr[0] "parent"
+msgstr[1] "parents"
+
diff --git a/locale/it/gitlab.po.time_stamp b/locale/it/gitlab.po.time_stamp
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/locale/pt_BR/gitlab.po b/locale/pt_BR/gitlab.po
index 5ad41f92b64f67c5eb88a19648d9d516fec679da..fe6d51c36ac2ca3fb1aafff28d2d0c6871cce345 100644
--- a/locale/pt_BR/gitlab.po
+++ b/locale/pt_BR/gitlab.po
@@ -250,6 +250,9 @@ msgstr "Precisa visualizar os dados? Solicite acesso ao administrador."
 msgid "We don't have enough data to show this stage."
 msgstr "Não temos dados suficientes para mostrar esta fase."
 
+msgid "You have reached your project limit"
+msgstr ""
+
 msgid "You need permission."
 msgstr "Você precisa de permissão."
 
diff --git a/locale/zh_CN/gitlab.po b/locale/zh_CN/gitlab.po
index 114344602075ea218932523f21315377589c8921..2f21aae2899ef81e6e2cca6ecb61386e194888bb 100644
--- a/locale/zh_CN/gitlab.po
+++ b/locale/zh_CN/gitlab.po
@@ -1,39 +1,268 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the gitlab package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
-#
+# Huang Tao <htve@outlook.com>, 2017. #zanata
+# Xiaogang Wen <xiaogang@gitlab.com>, 2017.
 msgid ""
 msgstr ""
 "Project-Id-Version: gitlab 1.0.0\n"
 "Report-Msgid-Bugs-To: \n"
-"PO-Revision-Date: 2017-05-04 19:24-0500\n"
-"Last-Translator: HuangTao <htve@outlook.com>, 2017\n"
-"Language-Team: Chinese (China) (https://www.transifex.com/gitlab-zh/teams/7517"
-"7/zh_CN/)\n"
+"POT-Creation-Date: 2017-06-19 15:50-0500\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
-"Language: zh_CN\n"
-"Plural-Forms: nplurals=1; plural=0;\n"
+"PO-Revision-Date: 2017-06-27 03:18-0400\n"
+"Last-Translator: Huang Tao <htve@outlook.com>\n"
+"Language-Team: Chinese (China) (https://translate.zanata.org/project/view/GitLab)\n"
+"Language: zh-CN\n"
+"X-Generator: Zanata 3.9.6\n"
+"Plural-Forms: nplurals=1; plural=0\n"
+
+msgid "%d additional commit has been omitted to prevent performance issues."
+msgid_plural ""
+"%d additional commits have been omitted to prevent performance issues."
+msgstr[0] "为提高页面加载速度及性能,已省略了 %d 次提交。"
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] "%d 次提交"
+
+msgid "%{commit_author_link} committed %{commit_timeago}"
+msgstr "由 %{commit_author_link} 提交于 %{commit_timeago}"
+
+msgid "About auto deploy"
+msgstr "关于自动部署"
+
+msgid "Active"
+msgstr "启用"
+
+msgid "Activity"
+msgstr "活动"
+
+msgid "Add Changelog"
+msgstr "添加更新日志"
+
+msgid "Add Contribution guide"
+msgstr "添加贡献指南"
+
+msgid "Add License"
+msgstr "添加许可证"
+
+msgid "Add an SSH key to your profile to pull or push via SSH."
+msgstr "新建一个用于推送或拉取的 SSH 秘钥到账号中。"
+
+msgid "Add new directory"
+msgstr "添加目录"
+
+msgid "Archived project! Repository is read-only"
+msgstr "项目已归档!存储库为只读状态"
 
 msgid "Are you sure you want to delete this pipeline schedule?"
+msgstr "确定要删除此流水线计划吗?"
+
+msgid "Attach a file by drag &amp; drop or %{upload_link}"
+msgstr "拖放文件到此处或者 %{upload_link}"
+
+msgid "Branch"
+msgid_plural "Branches"
+msgstr[0] "分支"
+
+msgid ""
+"Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, "
+"choose a GitLab CI Yaml template and commit your changes. "
+"%{link_to_autodeploy_doc}"
 msgstr ""
+"已创建分支 <strong>%{branch_name}</strong> 。如需设置自动部署, 请选择合适的 GitLab CI Yaml "
+"模板并提交更改。%{link_to_autodeploy_doc}"
+
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr "搜索分支"
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr "切换分支"
+
+msgid "Branches"
+msgstr "分支"
+
+msgid "Browse Directory"
+msgstr "浏览目录"
+
+msgid "Browse File"
+msgstr "浏览文件"
+
+msgid "Browse Files"
+msgstr "浏览文件"
+
+msgid "Browse files"
+msgstr "浏览文件"
 
 msgid "ByAuthor|by"
 msgstr "作者:"
 
+msgid "CI configuration"
+msgstr "CI 配置"
+
 msgid "Cancel"
-msgstr ""
+msgstr "取消"
+
+msgid "ChangeTypeActionLabel|Pick into branch"
+msgstr "选择分支"
+
+msgid "ChangeTypeActionLabel|Revert in branch"
+msgstr "还原分支"
+
+msgid "ChangeTypeAction|Cherry-pick"
+msgstr "优选"
+
+msgid "ChangeTypeAction|Revert"
+msgstr "还原"
+
+msgid "Changelog"
+msgstr "更新日志"
+
+msgid "Charts"
+msgstr "统计图"
+
+msgid "Cherry-pick this commit"
+msgstr "优选此提交"
+
+msgid "Cherry-pick this merge request"
+msgstr "优选此合并请求"
+
+msgid "CiStatusLabel|canceled"
+msgstr "已取消"
+
+msgid "CiStatusLabel|created"
+msgstr "已创建"
+
+msgid "CiStatusLabel|failed"
+msgstr "已失败"
+
+msgid "CiStatusLabel|manual action"
+msgstr "手动操作"
+
+msgid "CiStatusLabel|passed"
+msgstr "已通过"
+
+msgid "CiStatusLabel|passed with warnings"
+msgstr "已通过但有警告"
+
+msgid "CiStatusLabel|pending"
+msgstr "等待中"
+
+msgid "CiStatusLabel|skipped"
+msgstr "已跳过"
+
+msgid "CiStatusLabel|waiting for manual action"
+msgstr "等待手动操作"
+
+msgid "CiStatusText|blocked"
+msgstr "已阻塞"
+
+msgid "CiStatusText|canceled"
+msgstr "已取消"
+
+msgid "CiStatusText|created"
+msgstr "已创建"
+
+msgid "CiStatusText|failed"
+msgstr "已失败"
+
+msgid "CiStatusText|manual"
+msgstr "手动操作"
+
+msgid "CiStatusText|passed"
+msgstr "已通过"
+
+msgid "CiStatusText|pending"
+msgstr "等待中"
+
+msgid "CiStatusText|skipped"
+msgstr "已跳过"
+
+msgid "CiStatus|running"
+msgstr "运行中"
 
 msgid "Commit"
 msgid_plural "Commits"
 msgstr[0] "提交"
 
+msgid "Commit message"
+msgstr "提交信息"
+
+msgid "CommitBoxTitle|Commit"
+msgstr "提交"
+
+msgid "CommitMessage|Add %{file_name}"
+msgstr "添加 %{file_name}"
+
+msgid "Commits"
+msgstr "提交"
+
+msgid "Commits feed"
+msgstr "提交动态"
+
+msgid "Commits|History"
+msgstr "历史"
+
+msgid "Committed by"
+msgstr "提交者:"
+
+msgid "Compare"
+msgstr "比较"
+
+msgid "Contribution guide"
+msgstr "贡献指南"
+
+msgid "Contributors"
+msgstr "贡献者"
+
+msgid "Copy URL to clipboard"
+msgstr "复制 URL 到剪贴板"
+
+msgid "Copy commit SHA to clipboard"
+msgstr "复制提交 SHA 的值到剪贴板"
+
+msgid "Create New Directory"
+msgstr "创建新目录"
+
+msgid "Create directory"
+msgstr "创建目录"
+
+msgid "Create empty bare repository"
+msgstr "创建空的存储库"
+
+msgid "Create merge request"
+msgstr "创建合并请求"
+
+msgid "Create new..."
+msgstr "创建..."
+
+msgid "CreateNewFork|Fork"
+msgstr "派生"
+
+msgid "CreateTag|Tag"
+msgstr "标签"
+
 msgid "Cron Timezone"
+msgstr "Cron 时区"
+
+msgid "Cron syntax"
+msgstr "Cron 语法"
+
+msgid "Custom notification events"
+msgstr "自定义通知事件"
+
+msgid ""
+"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 %{notification_link}."
 msgstr ""
+"自定义通知级别继承自参与级别。使用自定义通知级别,您会收到参与级别及选定事件的通知。想了解更多信息,请查看 %{notification_link}."
 
-msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project."
+msgid "Cycle Analytics"
+msgstr "周期分析"
+
+msgid ""
+"Cycle Analytics gives an overview of how much time it takes to go from idea "
+"to production in your project."
 msgstr "周期分析概述了项目从想法到产品实现的各阶段所需的时间。"
 
 msgid "CycleAnalyticsStage|Code"
@@ -57,30 +286,84 @@ msgstr "预发布"
 msgid "CycleAnalyticsStage|Test"
 msgstr "测试"
 
+msgid "Define a custom pattern with cron syntax"
+msgstr "使用 Cron 语法定义自定义模式"
+
 msgid "Delete"
-msgstr ""
+msgstr "删除"
 
 msgid "Deploy"
 msgid_plural "Deploys"
 msgstr[0] "部署"
 
 msgid "Description"
-msgstr ""
+msgstr "描述"
+
+msgid "Directory name"
+msgstr "目录名称"
+
+msgid "Don't show again"
+msgstr "不再显示"
+
+msgid "Download"
+msgstr "下载"
+
+msgid "Download tar"
+msgstr "下载 tar"
+
+msgid "Download tar.bz2"
+msgstr "下载 tar.bz2"
+
+msgid "Download tar.gz"
+msgstr "下载 tar.gz"
+
+msgid "Download zip"
+msgstr "下载 zip"
+
+msgid "DownloadArtifacts|Download"
+msgstr "下载"
+
+msgid "DownloadCommit|Email Patches"
+msgstr "电子邮件补丁"
+
+msgid "DownloadCommit|Plain Diff"
+msgstr "差异文件"
+
+msgid "DownloadSource|Download"
+msgstr "下载"
 
 msgid "Edit"
-msgstr ""
+msgstr "编辑"
 
 msgid "Edit Pipeline Schedule %{id}"
-msgstr ""
+msgstr "编辑 %{id} 流水线计划"
+
+msgid "Every day (at 4:00am)"
+msgstr "每日执行(凌晨 4 点)"
+
+msgid "Every month (on the 1st at 4:00am)"
+msgstr "每月执行(每月 1 日凌晨 4 点)"
+
+msgid "Every week (Sundays at 4:00am)"
+msgstr "每周执行(周日凌晨 4 点)"
 
 msgid "Failed to change the owner"
-msgstr ""
+msgstr "无法变更所有者"
 
 msgid "Failed to remove the pipeline schedule"
-msgstr ""
+msgstr "无法删除流水线计划"
 
-msgid "Filter"
-msgstr ""
+msgid "Files"
+msgstr "文件"
+
+msgid "Filter by commit message"
+msgstr "按提交消息过滤"
+
+msgid "Find by path"
+msgstr "按路径查找"
+
+msgid "Find file"
+msgstr "查找文件"
 
 msgid "FirstPushedBy|First"
 msgstr "首次推送"
@@ -88,24 +371,70 @@ msgstr "首次推送"
 msgid "FirstPushedBy|pushed by"
 msgstr "推送者:"
 
+msgid "Fork"
+msgid_plural "Forks"
+msgstr[0] "派生"
+
+msgid "ForkedFromProjectPath|Forked from"
+msgstr "派生自"
+
 msgid "From issue creation until deploy to production"
 msgstr "从创建议题到部署至生产环境"
 
 msgid "From merge request merge until deploy to production"
 msgstr "从合并请求被合并后到部署至生产环境"
 
+msgid "Go to your fork"
+msgstr "跳转到派生项目"
+
+msgid "GoToYourFork|Fork"
+msgstr "跳转到派生项目"
+
+msgid "Home"
+msgstr "首页"
+
+msgid "Housekeeping successfully started"
+msgstr "已开始维护"
+
+msgid "Import repository"
+msgstr "导入存储库"
+
 msgid "Interval Pattern"
-msgstr ""
+msgstr "循环周期"
 
 msgid "Introducing Cycle Analytics"
 msgstr "周期分析简介"
 
+msgid "LFSStatus|Disabled"
+msgstr "停用"
+
+msgid "LFSStatus|Enabled"
+msgstr "启用"
+
 msgid "Last %d day"
 msgid_plural "Last %d days"
-msgstr[0] "最后 %d 天"
+msgstr[0] "最近 %d 天"
 
 msgid "Last Pipeline"
-msgstr ""
+msgstr "最新流水线"
+
+msgid "Last Update"
+msgstr "最后更新"
+
+msgid "Last commit"
+msgstr "最后提交"
+
+msgid "Learn more in the"
+msgstr "了解更多"
+
+msgid "Learn more in the|pipeline schedules documentation"
+msgstr "流水线计划文档"
+
+msgid "Leave group"
+msgstr "退出群组"
+
+msgid "Leave project"
+msgstr "退出项目"
 
 msgid "Limited to showing %d event at most"
 msgid_plural "Limited to showing %d events at most"
@@ -114,15 +443,45 @@ msgstr[0] "最多显示 %d 个事件"
 msgid "Median"
 msgstr "中位数"
 
+msgid "MissingSSHKeyWarningLink|add an SSH key"
+msgstr "新建 SSH 公钥"
+
 msgid "New Issue"
 msgid_plural "New Issues"
-msgstr[0] "新议题"
+msgstr[0] "新建议题"
 
 msgid "New Pipeline Schedule"
-msgstr ""
+msgstr "创建流水线计划"
+
+msgid "New branch"
+msgstr "新建分支"
+
+msgid "New directory"
+msgstr "新建目录"
+
+msgid "New file"
+msgstr "新建文件"
+
+msgid "New issue"
+msgstr "新建议题"
+
+msgid "New merge request"
+msgstr "新建合并请求"
+
+msgid "New schedule"
+msgstr "新建计划"
+
+msgid "New snippet"
+msgstr "新建代码片段"
+
+msgid "New tag"
+msgstr "新建标签"
+
+msgid "No repository"
+msgstr "没有存储库"
 
 msgid "No schedules"
-msgstr ""
+msgstr "没有计划"
 
 msgid "Not available"
 msgstr "数据不足"
@@ -130,54 +489,185 @@ msgstr "数据不足"
 msgid "Not enough data"
 msgstr "数据不足"
 
+msgid "Notification events"
+msgstr "通知事件"
+
+msgid "NotificationEvent|Close issue"
+msgstr "关闭议题"
+
+msgid "NotificationEvent|Close merge request"
+msgstr "关闭合并请求"
+
+msgid "NotificationEvent|Failed pipeline"
+msgstr "流水线失败"
+
+msgid "NotificationEvent|Merge merge request"
+msgstr "合并请求被合并"
+
+msgid "NotificationEvent|New issue"
+msgstr "新建议题"
+
+msgid "NotificationEvent|New merge request"
+msgstr "新建合并请求"
+
+msgid "NotificationEvent|New note"
+msgstr "新建评论"
+
+msgid "NotificationEvent|Reassign issue"
+msgstr "重新指派议题"
+
+msgid "NotificationEvent|Reassign merge request"
+msgstr "重新指派合并请求"
+
+msgid "NotificationEvent|Reopen issue"
+msgstr "重启议题"
+
+msgid "NotificationEvent|Successful pipeline"
+msgstr "流水线成功完成"
+
+msgid "NotificationLevel|Custom"
+msgstr "自定义"
+
+msgid "NotificationLevel|Disabled"
+msgstr "停用"
+
+msgid "NotificationLevel|Global"
+msgstr "全局"
+
+msgid "NotificationLevel|On mention"
+msgstr "提及"
+
+msgid "NotificationLevel|Participate"
+msgstr "参与"
+
+msgid "NotificationLevel|Watch"
+msgstr "关注"
+
+msgid "OfSearchInADropdown|Filter"
+msgstr "筛选"
+
 msgid "OpenedNDaysAgo|Opened"
 msgstr "开始于"
 
+msgid "Options"
+msgstr "操作"
+
 msgid "Owner"
-msgstr ""
+msgstr "所有者"
+
+msgid "Pipeline"
+msgstr "流水线"
 
 msgid "Pipeline Health"
 msgstr "流水线健康指标"
 
 msgid "Pipeline Schedule"
-msgstr ""
+msgstr "流水线计划"
 
 msgid "Pipeline Schedules"
-msgstr ""
+msgstr "流水线计划"
 
 msgid "PipelineSchedules|Activated"
-msgstr ""
+msgstr "是否启用"
 
 msgid "PipelineSchedules|Active"
-msgstr ""
+msgstr "已启用"
 
 msgid "PipelineSchedules|All"
-msgstr ""
+msgstr "所有"
 
 msgid "PipelineSchedules|Inactive"
-msgstr ""
+msgstr "未启用"
 
 msgid "PipelineSchedules|Next Run"
-msgstr ""
+msgstr "下次运行时间"
 
 msgid "PipelineSchedules|None"
-msgstr ""
+msgstr "无"
 
 msgid "PipelineSchedules|Provide a short description for this pipeline"
-msgstr ""
+msgstr "为此流水线提供简短描述"
 
 msgid "PipelineSchedules|Take ownership"
-msgstr ""
+msgstr "取得所有者"
 
 msgid "PipelineSchedules|Target"
-msgstr ""
+msgstr "目标"
+
+msgid "PipelineSheduleIntervalPattern|Custom"
+msgstr "自定义"
+
+msgid "Pipeline|with stage"
+msgstr "于阶段"
+
+msgid "Pipeline|with stages"
+msgstr "于阶段"
+
+msgid "Project '%{project_name}' queued for deletion."
+msgstr "项目 '%{project_name}' 已进入删除队列。"
+
+msgid "Project '%{project_name}' was successfully created."
+msgstr "项目 '%{project_name}' 已创建成功。"
+
+msgid "Project '%{project_name}' was successfully updated."
+msgstr "项目 '%{project_name}' 已更新完成。"
+
+msgid "Project '%{project_name}' will be deleted."
+msgstr "项目 '%{project_name}' 将被删除。"
+
+msgid "Project access must be granted explicitly to each user."
+msgstr "项目访问权限必须明确授权给每个用户。"
+
+msgid "Project export could not be deleted."
+msgstr "无法删除项目导出。"
+
+msgid "Project export has been deleted."
+msgstr "项目导出已被删除。"
+
+msgid ""
+"Project export link has expired. Please generate a new export from your "
+"project settings."
+msgstr "项目导出链接已过期。请从项目设置中重新生成项目导出。"
+
+msgid "Project export started. A download link will be sent by email."
+msgstr "项目导出已开始。下载链接将通过电子邮件发送。"
+
+msgid "Project home"
+msgstr "项目首页"
+
+msgid "ProjectFeature|Disabled"
+msgstr "停用"
+
+msgid "ProjectFeature|Everyone with access"
+msgstr "任何对项目有访问权的人"
+
+msgid "ProjectFeature|Only team members"
+msgstr "只限团队成员"
+
+msgid "ProjectFileTree|Name"
+msgstr "名称"
+
+msgid "ProjectLastActivity|Never"
+msgstr "从未"
 
 msgid "ProjectLifecycle|Stage"
-msgstr "项目生命周期"
+msgstr "阶段"
+
+msgid "ProjectNetworkGraph|Graph"
+msgstr "分支图"
 
 msgid "Read more"
 msgstr "了解更多"
 
+msgid "Readme"
+msgstr "自述文件"
+
+msgid "RefSwitcher|Branches"
+msgstr "分支"
+
+msgid "RefSwitcher|Tags"
+msgstr "标签"
+
 msgid "Related Commits"
 msgstr "相关的提交"
 
@@ -196,58 +686,163 @@ msgstr "相关的合并请求"
 msgid "Related Merged Requests"
 msgstr "相关已合并的合并请求"
 
+msgid "Remind later"
+msgstr "稍后提醒"
+
+msgid "Remove project"
+msgstr "删除项目"
+
+msgid "Request Access"
+msgstr "申请权限"
+
+msgid "Revert this commit"
+msgstr "还原此提交"
+
+msgid "Revert this merge request"
+msgstr "还原此合并请求"
+
 msgid "Save pipeline schedule"
-msgstr ""
+msgstr "保存流水线计划"
 
 msgid "Schedule a new pipeline"
-msgstr ""
+msgstr "新建流水线计划"
+
+msgid "Scheduling Pipelines"
+msgstr "流水线计划"
+
+msgid "Search branches and tags"
+msgstr "搜索分支和标签"
+
+msgid "Select Archive Format"
+msgstr "选择下载格式"
 
 msgid "Select a timezone"
-msgstr ""
+msgstr "选择时区"
 
 msgid "Select target branch"
-msgstr ""
+msgstr "选择目标分支"
+
+msgid "Set a password on your account to pull or push via %{protocol}."
+msgstr "为账号创建一个用于推送或拉取的 %{protocol} 密码。"
+
+msgid "Set up CI"
+msgstr "设置 CI"
+
+msgid "Set up Koding"
+msgstr "设置 Koding"
+
+msgid "Set up auto deploy"
+msgstr "设置自动部署"
+
+msgid "SetPasswordToCloneLink|set a password"
+msgstr "设置密码"
 
 msgid "Showing %d event"
 msgid_plural "Showing %d events"
 msgstr[0] "显示 %d 个事件"
 
+msgid "Source code"
+msgstr "源代码"
+
+msgid "StarProject|Star"
+msgstr "星标"
+
+msgid "Start a %{new_merge_request} with these changes"
+msgstr "由此更改 %{new_merge_request}"
+
+msgid "Switch branch/tag"
+msgstr "切换分支/标签"
+
+msgid "Tag"
+msgid_plural "Tags"
+msgstr[0] "标签"
+
+msgid "Tags"
+msgstr "标签"
+
 msgid "Target Branch"
-msgstr ""
+msgstr "目标分支"
 
-msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request."
+msgid ""
+"The coding stage shows the time from the first commit to creating the merge "
+"request. The data will automatically be added here once you create your "
+"first merge request."
 msgstr "编码阶段概述了从第一次提交到创建合并请求的时间。创建第一个合并请求后,数据将自动添加到此处。"
 
 msgid "The collection of events added to the data gathered for that stage."
-msgstr "与该阶段相关的事件。"
+msgstr "与该阶段相关的事件集合。"
 
-msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage."
-msgstr "议题阶段概述了从创建议题到将议题设置里程碑或将议题添加到议题看板的时间。开始创建议题以查看此阶段的数据。"
+msgid "The fork relationship has been removed."
+msgstr "派生关系已被删除。"
+
+msgid ""
+"The issue stage shows the time it takes from creating an issue to assigning "
+"the issue to a milestone, or add the issue to a list on your Issue Board. "
+"Begin creating issues to see data for this stage."
+msgstr "议题阶段概述了从创建议题到将议题添加到里程碑或议题看板所花费的时间。创建第一个议题后,数据将自动添加到此处.。"
 
 msgid "The phase of the development lifecycle."
 msgstr "项目生命周期中的各个阶段。"
 
-msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit."
-msgstr "计划阶段概述了从议题添加到日程后到推送首次提交的时间。当首次推送提交后,数据将自动添加到此处。"
+msgid ""
+"The pipelines schedule runs pipelines in the future, repeatedly, for "
+"specific branches or tags. Those scheduled pipelines will inherit limited "
+"project access based on their associated user."
+msgstr "流水线计划会周期性重复运行指定分支或标签的流水线。这些流水线将根据其关联用户继承有限的项目访问权限。"
+
+msgid ""
+"The planning stage shows the time from the previous step to pushing your "
+"first commit. This time will be added automatically once you push your first "
+"commit."
+msgstr "计划阶段概述了从议题添加到日程到推送首次提交的时间。当首次推送提交后,数据将自动添加到此处。"
 
-msgid "The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
+msgid ""
+"The production stage shows the total time it takes between creating an issue "
+"and deploying the code to production. The data will be automatically added "
+"once you have completed the full idea to production cycle."
 msgstr "生产阶段概述了从创建一个议题到将代码部署到生产环境的总时间。当完成想法到部署生产的循环,数据将自动添加到此处。"
 
-msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request."
+msgid "The project can be accessed by any logged in user."
+msgstr "该项目允许已登录的用户访问。"
+
+msgid "The project can be accessed without any authentication."
+msgstr "该项目允许任何人访问。"
+
+msgid "The repository for this project does not exist."
+msgstr "此项目的存储库不存在。"
+
+msgid ""
+"The review stage shows the time from creating the merge request to merging "
+"it. The data will automatically be added after you merge your first merge "
+"request."
 msgstr "评审阶段概述了从创建合并请求到被合并的时间。当创建第一个合并请求后,数据将自动添加到此处。"
 
-msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time."
+msgid ""
+"The staging stage shows the time between merging the MR and deploying code "
+"to the production environment. The data will be automatically added once you "
+"deploy to production for the first time."
 msgstr "预发布阶段概述了从合并请求被合并到部署至生产环境的总时间。首次部署到生产环境后,数据将自动添加到此处。"
 
-msgid "The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running."
-msgstr "测试阶段概述了GitLab CI为相关合并请求运行每个流水线所需的时间。当第一个流水线运行完成后,数据将自动添加到此处。"
+msgid ""
+"The testing stage shows the time GitLab CI takes to run every pipeline for "
+"the related merge request. The data will automatically be added after your "
+"first pipeline finishes running."
+msgstr "测试阶段概述了 GitLab CI 为相关合并请求运行每个流水线所需的时间。当第一个流水线运行完成后,数据将自动添加到此处。"
 
 msgid "The time taken by each data entry gathered by that stage."
 msgstr "该阶段每条数据所花的时间"
 
-msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
+msgid ""
+"The value lying at the midpoint of a series of observed values. E.g., "
+"between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 ="
+" 6."
 msgstr "中位数是一个数列中最中间的值。例如在 3、5、9 之间,中位数是 5。在 3、5、7、8 之间,中位数是 (5 + 7)/ 2 = 6。"
 
+msgid ""
+"This means you can not push code until you create an empty repository or "
+"import existing one."
+msgstr "在创建一个空的存储库或导入现有存储库之前,将无法推送代码。"
+
 msgid "Time before an issue gets scheduled"
 msgstr "议题被列入日程表的时间"
 
@@ -260,6 +855,129 @@ msgstr "从创建合并请求到被合并或关闭的时间"
 msgid "Time until first merge request"
 msgstr "创建第一个合并请求之前的时间"
 
+msgid "Timeago|%s days ago"
+msgstr " %s 天前"
+
+msgid "Timeago|%s days remaining"
+msgstr "剩余 %s 天"
+
+msgid "Timeago|%s hours remaining"
+msgstr "剩余 %s 小时"
+
+msgid "Timeago|%s minutes ago"
+msgstr " %s 分钟前"
+
+msgid "Timeago|%s minutes remaining"
+msgstr "剩余 %s 分钟"
+
+msgid "Timeago|%s months ago"
+msgstr " %s 个月前"
+
+msgid "Timeago|%s months remaining"
+msgstr "剩余 %s 月"
+
+msgid "Timeago|%s seconds remaining"
+msgstr "剩余 %s 秒"
+
+msgid "Timeago|%s weeks ago"
+msgstr " %s 星期前"
+
+msgid "Timeago|%s weeks remaining"
+msgstr "剩余 %s 星期"
+
+msgid "Timeago|%s years ago"
+msgstr " %s 年前"
+
+msgid "Timeago|%s years remaining"
+msgstr "剩余 %s 年"
+
+msgid "Timeago|1 day remaining"
+msgstr "剩余 1 天"
+
+msgid "Timeago|1 hour remaining"
+msgstr "剩余 1 小时"
+
+msgid "Timeago|1 minute remaining"
+msgstr "剩余 1 分钟"
+
+msgid "Timeago|1 month remaining"
+msgstr "剩余 1 个月"
+
+msgid "Timeago|1 week remaining"
+msgstr "剩余 1 星期"
+
+msgid "Timeago|1 year remaining"
+msgstr "剩余 1 年"
+
+msgid "Timeago|Past due"
+msgstr "逾期"
+
+msgid "Timeago|a day ago"
+msgstr " 1 天前"
+
+msgid "Timeago|a month ago"
+msgstr " 1 个月前"
+
+msgid "Timeago|a week ago"
+msgstr " 1 星期前"
+
+msgid "Timeago|a while"
+msgstr "刚刚"
+
+msgid "Timeago|a year ago"
+msgstr " 1 年前"
+
+msgid "Timeago|about %s hours ago"
+msgstr "约 %s 小时前"
+
+msgid "Timeago|about a minute ago"
+msgstr "约 1 分钟前"
+
+msgid "Timeago|about an hour ago"
+msgstr "约 1 小时前"
+
+msgid "Timeago|in %s days"
+msgstr " %s 天后"
+
+msgid "Timeago|in %s hours"
+msgstr " %s 小时后"
+
+msgid "Timeago|in %s minutes"
+msgstr " %s 分钟后"
+
+msgid "Timeago|in %s months"
+msgstr " %s 个月后"
+
+msgid "Timeago|in %s seconds"
+msgstr " %s 秒后"
+
+msgid "Timeago|in %s weeks"
+msgstr " %s 星期后"
+
+msgid "Timeago|in %s years"
+msgstr " %s 年后"
+
+msgid "Timeago|in 1 day"
+msgstr " 1 天后"
+
+msgid "Timeago|in 1 hour"
+msgstr " 1 小时后"
+
+msgid "Timeago|in 1 minute"
+msgstr " 1 分钟后"
+
+msgid "Timeago|in 1 month"
+msgstr " 1 月后"
+
+msgid "Timeago|in 1 week"
+msgstr " 1 星期后"
+
+msgid "Timeago|in 1 year"
+msgstr " 1 年后"
+
+msgid "Timeago|less than a minute ago"
+msgstr "不到 1 分钟前"
+
 msgid "Time|hr"
 msgid_plural "Time|hrs"
 msgstr[0] "小时"
@@ -277,15 +995,114 @@ msgstr "总时间"
 msgid "Total test time for all commits/merges"
 msgstr "所有提交和合并的总测试时间"
 
+msgid "Unstar"
+msgstr "取消星标"
+
+msgid "Upload New File"
+msgstr "上传新文件"
+
+msgid "Upload file"
+msgstr "上传文件"
+
+msgid "UploadLink|click to upload"
+msgstr "点击上传"
+
+msgid "Use your global notification setting"
+msgstr "使用全局通知设置"
+
+msgid "View open merge request"
+msgstr "查看待处理的合并请求"
+
+msgid "VisibilityLevel|Internal"
+msgstr "内部"
+
+msgid "VisibilityLevel|Private"
+msgstr "私有"
+
+msgid "VisibilityLevel|Public"
+msgstr "公开"
+
 msgid "Want to see the data? Please ask an administrator for access."
 msgstr "权限不足。如需查看相关数据,请向管理员申请权限。"
 
 msgid "We don't have enough data to show this stage."
 msgstr "该阶段的数据不足,无法显示。"
 
+msgid "Withdraw Access Request"
+msgstr "取消权限申请"
+
+msgid ""
+"You are going to remove %{project_name_with_namespace}.\n"
+"Removed project CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr "即将要删除 %{project_name_with_namespace}。\n"
+"已删除的项目无法恢复!\n"
+"确定继续吗?"
+
+msgid ""
+"You are going to remove the fork relationship to source project "
+"%{forked_from_project}. Are you ABSOLUTELY sure?"
+msgstr "即将删除与源项目 %{forked_from_project} 的派生关系。确定继续吗?"
+
+msgid ""
+"You are going to transfer %{project_name_with_namespace} to another owner. "
+"Are you ABSOLUTELY sure?"
+msgstr "即将 %{project_name_with_namespace} 转移给另一个所有者。确定继续吗?"
+
+msgid "You can only add files when you are on a branch"
+msgstr "只能在分支上添加文件"
+
+msgid "You have reached your project limit"
+msgstr "您已达到项目数量限制"
+
+msgid "You must sign in to star a project"
+msgstr "必须登录才能对项目加星标"
+
 msgid "You need permission."
-msgstr "您需要相关的权限。"
+msgstr "需要相关的权限。"
+
+msgid "You will not get any notifications via email"
+msgstr "不会收到任何通知邮件"
+
+msgid "You will only receive notifications for the events you choose"
+msgstr "只接收选择的事件通知"
+
+msgid ""
+"You will only receive notifications for threads you have participated in"
+msgstr "只接收参与的主题的通知"
+
+msgid "You will receive notifications for any activity"
+msgstr "接收所有活动的通知"
+
+msgid ""
+"You will receive notifications only for comments in which you were "
+"@mentioned"
+msgstr "只接收评论中提及(@)您的通知"
+
+msgid ""
+"You won't be able to pull or push project code via %{protocol} until you "
+"%{set_password_link} on your account"
+msgstr "在账号中 %{set_password_link} 之前将无法通过 %{protocol} 拉取或推送代码。"
+
+msgid ""
+"You won't be able to pull or push project code via SSH until you "
+"%{add_ssh_key_link} to your profile"
+msgstr "在账号中 %{add_ssh_key_link} 之前将无法通过 SSH 拉取或推送代码。"
+
+msgid "Your name"
+msgstr "您的名字"
 
 msgid "day"
 msgid_plural "days"
 msgstr[0] "天"
+
+msgid "new merge request"
+msgstr "新建合并请求"
+
+msgid "notification emails"
+msgstr "通知邮件"
+
+msgid "parent"
+msgid_plural "parents"
+msgstr[0] "父级"
+
diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po
index 81b2ff863eaa9e71962baa5c3f10e98a4eebab06..afdbd01b7d79f98d529bba7fd2e8056735a4bf98 100644
--- a/locale/zh_HK/gitlab.po
+++ b/locale/zh_HK/gitlab.po
@@ -1,39 +1,269 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the gitlab package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
-#
+# Huang Tao <htve@outlook.com>, 2017. #zanata
+# Victor Wu <anonymous@domain.com>, 2017.
+# Hazel Yang <anonymous@domain.com>, 2017.
 msgid ""
 msgstr ""
 "Project-Id-Version: gitlab 1.0.0\n"
 "Report-Msgid-Bugs-To: \n"
-"PO-Revision-Date: 2017-05-04 19:24-0500\n"
-"Last-Translator: HuangTao <htve@outlook.com>, 2017\n"
-"Language-Team: Chinese (Hong Kong) (https://www.transifex.com/gitlab-zh/teams/"
-"75177/zh_HK/)\n"
+"POT-Creation-Date: 2017-06-15 21:59-0500\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
-"Language: zh_HK\n"
-"Plural-Forms: nplurals=1; plural=0;\n"
+"PO-Revision-Date: 2017-06-23 01:23-0400\n"
+"Last-Translator: Huang Tao <htve@outlook.com>\n"
+"Language-Team: Chinese (Hong Kong) (https://translate.zanata.org/project/view/GitLab)\n"
+"Language: zh-HK\n"
+"X-Generator: Zanata 3.9.6\n"
+"Plural-Forms: nplurals=1; plural=0\n"
+
+msgid "%d additional commit has been omitted to prevent performance issues."
+msgid_plural ""
+"%d additional commits have been omitted to prevent performance issues."
+msgstr[0] "為提高頁面加載速度及性能,已省略了 %d 次提交。"
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] " %d 次提交"
+
+msgid "%{commit_author_link} committed %{commit_timeago}"
+msgstr "由 %{commit_author_link} 提交於 %{commit_timeago}"
+
+msgid "About auto deploy"
+msgstr "關於自動部署"
+
+msgid "Active"
+msgstr "啟用"
+
+msgid "Activity"
+msgstr "活動"
+
+msgid "Add Changelog"
+msgstr "添加更新日誌"
+
+msgid "Add Contribution guide"
+msgstr "添加貢獻指南"
+
+msgid "Add License"
+msgstr "添加許可證"
+
+msgid "Add an SSH key to your profile to pull or push via SSH."
+msgstr "新增壹個用於推送或拉取的 SSH 秘鑰到賬號中。"
+
+msgid "Add new directory"
+msgstr "添加新目錄"
+
+msgid "Archived project! Repository is read-only"
+msgstr "歸檔項目!存儲庫為只讀"
 
 msgid "Are you sure you want to delete this pipeline schedule?"
+msgstr "確定要刪除此流水線計劃嗎?"
+
+msgid "Attach a file by drag &amp; drop or %{upload_link}"
+msgstr "拖放文件到此處或者 %{upload_link}"
+
+msgid "Branch"
+msgid_plural "Branches"
+msgstr[0] "分支"
+
+msgid ""
+"Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, "
+"choose a GitLab CI Yaml template and commit your changes. "
+"%{link_to_autodeploy_doc}"
 msgstr ""
+"分支 <strong>%{branch_name}</strong> 已創建。如需設置自動部署, 請選擇合適的 GitLab CI Yaml "
+"模板併提交更改。%{link_to_autodeploy_doc}"
+
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr "搜索分支"
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr "切換分支"
+
+msgid "Branches"
+msgstr "分支"
+
+msgid "Browse Directory"
+msgstr "瀏覽目錄"
+
+msgid "Browse File"
+msgstr "瀏覽文件"
+
+msgid "Browse Files"
+msgstr "瀏覽文件"
+
+msgid "Browse files"
+msgstr "瀏覽文件"
 
 msgid "ByAuthor|by"
 msgstr "作者:"
 
+msgid "CI configuration"
+msgstr "CI 配置"
+
 msgid "Cancel"
-msgstr ""
+msgstr "取消"
+
+msgid "ChangeTypeActionLabel|Pick into branch"
+msgstr "挑選到分支"
+
+msgid "ChangeTypeActionLabel|Revert in branch"
+msgstr "還原分支"
+
+msgid "ChangeTypeAction|Cherry-pick"
+msgstr "優選"
+
+msgid "ChangeTypeAction|Revert"
+msgstr "還原"
+
+msgid "Changelog"
+msgstr "更新日誌"
+
+msgid "Charts"
+msgstr "統計圖"
+
+msgid "Cherry-pick this commit"
+msgstr "優選此提交"
+
+msgid "Cherry-pick this merge request"
+msgstr "優選此合併請求"
+
+msgid "CiStatusLabel|canceled"
+msgstr "已取消"
+
+msgid "CiStatusLabel|created"
+msgstr "已創建"
+
+msgid "CiStatusLabel|failed"
+msgstr "已失敗"
+
+msgid "CiStatusLabel|manual action"
+msgstr "手動操作"
+
+msgid "CiStatusLabel|passed"
+msgstr "已通過"
+
+msgid "CiStatusLabel|passed with warnings"
+msgstr "已通過但有警告"
+
+msgid "CiStatusLabel|pending"
+msgstr "等待中"
+
+msgid "CiStatusLabel|skipped"
+msgstr "已跳過"
+
+msgid "CiStatusLabel|waiting for manual action"
+msgstr "等待手動操作"
+
+msgid "CiStatusText|blocked"
+msgstr "已阻塞"
+
+msgid "CiStatusText|canceled"
+msgstr "已取消"
+
+msgid "CiStatusText|created"
+msgstr "已創建"
+
+msgid "CiStatusText|failed"
+msgstr "已失敗"
+
+msgid "CiStatusText|manual"
+msgstr "待手動"
+
+msgid "CiStatusText|passed"
+msgstr "已通過"
+
+msgid "CiStatusText|pending"
+msgstr "等待中"
+
+msgid "CiStatusText|skipped"
+msgstr "已跳過"
+
+msgid "CiStatus|running"
+msgstr "運行中"
 
 msgid "Commit"
 msgid_plural "Commits"
 msgstr[0] "提交"
 
+msgid "Commit message"
+msgstr "提交信息"
+
+msgid "CommitBoxTitle|Commit"
+msgstr "提交"
+
+msgid "CommitMessage|Add %{file_name}"
+msgstr "添加 %{file_name}"
+
+msgid "Commits"
+msgstr "提交"
+
+msgid "Commits feed"
+msgstr "提交動態"
+
+msgid "Commits|History"
+msgstr "歷史"
+
+msgid "Committed by"
+msgstr "提交者:"
+
+msgid "Compare"
+msgstr "比較"
+
+msgid "Contribution guide"
+msgstr "貢獻指南"
+
+msgid "Contributors"
+msgstr "貢獻者"
+
+msgid "Copy URL to clipboard"
+msgstr "複製URL到剪貼板"
+
+msgid "Copy commit SHA to clipboard"
+msgstr "複製提交 SHA 到剪貼板"
+
+msgid "Create New Directory"
+msgstr "創建新目錄"
+
+msgid "Create directory"
+msgstr "創建目錄"
+
+msgid "Create empty bare repository"
+msgstr "創建空的存儲庫"
+
+msgid "Create merge request"
+msgstr "創建合併請求"
+
+msgid "Create new..."
+msgstr "創建..."
+
+msgid "CreateNewFork|Fork"
+msgstr "派生"
+
+msgid "CreateTag|Tag"
+msgstr "標籤"
+
 msgid "Cron Timezone"
+msgstr "Cron 時區"
+
+msgid "Cron syntax"
+msgstr "Cron 語法"
+
+msgid "Custom notification events"
+msgstr "自定義通知事件"
+
+msgid ""
+"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 %{notification_link}."
 msgstr ""
+"自定義通知級別繼承自參與級別。使用自定義通知級別,您會收到參與級別及選定事件的通知。想了解更多信息,請查看 %{notification_link}."
 
-msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project."
+msgid "Cycle Analytics"
+msgstr "週期分析"
+
+msgid ""
+"Cycle Analytics gives an overview of how much time it takes to go from idea "
+"to production in your project."
 msgstr "週期分析概述了項目從想法到產品實現的各階段所需的時間。"
 
 msgid "CycleAnalyticsStage|Code"
@@ -57,30 +287,84 @@ msgstr "預發布"
 msgid "CycleAnalyticsStage|Test"
 msgstr "測試"
 
+msgid "Define a custom pattern with cron syntax"
+msgstr "使用 Cron 語法定義自定義模式"
+
 msgid "Delete"
-msgstr ""
+msgstr "刪除"
 
 msgid "Deploy"
 msgid_plural "Deploys"
 msgstr[0] "部署"
 
 msgid "Description"
-msgstr ""
+msgstr "描述"
+
+msgid "Directory name"
+msgstr "目錄名稱"
+
+msgid "Don't show again"
+msgstr "不再顯示"
+
+msgid "Download"
+msgstr "下載"
+
+msgid "Download tar"
+msgstr "下載 tar"
+
+msgid "Download tar.bz2"
+msgstr "下載 tar.bz2"
+
+msgid "Download tar.gz"
+msgstr "下載 tar.gz"
+
+msgid "Download zip"
+msgstr "下載 zip"
+
+msgid "DownloadArtifacts|Download"
+msgstr "下載"
+
+msgid "DownloadCommit|Email Patches"
+msgstr "電子郵件補丁"
+
+msgid "DownloadCommit|Plain Diff"
+msgstr "差異文件"
+
+msgid "DownloadSource|Download"
+msgstr "下載"
 
 msgid "Edit"
-msgstr ""
+msgstr "編輯"
 
 msgid "Edit Pipeline Schedule %{id}"
-msgstr ""
+msgstr "編輯 %{id} 流水線計劃"
+
+msgid "Every day (at 4:00am)"
+msgstr "每日執行(淩晨 4 點)"
+
+msgid "Every month (on the 1st at 4:00am)"
+msgstr "每月執行(每月 1 日淩晨 4 點)"
+
+msgid "Every week (Sundays at 4:00am)"
+msgstr "每週執行(周日淩晨 4 點)"
 
 msgid "Failed to change the owner"
-msgstr ""
+msgstr "無法變更所有者"
 
 msgid "Failed to remove the pipeline schedule"
-msgstr ""
+msgstr "無法刪除流水線計劃"
 
-msgid "Filter"
-msgstr ""
+msgid "Files"
+msgstr "文件"
+
+msgid "Filter by commit message"
+msgstr "按提交消息過濾"
+
+msgid "Find by path"
+msgstr "按路徑查找"
+
+msgid "Find file"
+msgstr "查找文件"
 
 msgid "FirstPushedBy|First"
 msgstr "首次推送"
@@ -88,24 +372,70 @@ msgstr "首次推送"
 msgid "FirstPushedBy|pushed by"
 msgstr "推送者:"
 
+msgid "Fork"
+msgid_plural "Forks"
+msgstr[0] "派生"
+
+msgid "ForkedFromProjectPath|Forked from"
+msgstr "派生自"
+
 msgid "From issue creation until deploy to production"
 msgstr "從創建議題到部署到生產環境"
 
 msgid "From merge request merge until deploy to production"
 msgstr "從合併請求的合併到部署至生產環境"
 
+msgid "Go to your fork"
+msgstr "跳轉到派生項目"
+
+msgid "GoToYourFork|Fork"
+msgstr "跳轉到派生項目"
+
+msgid "Home"
+msgstr "首頁"
+
+msgid "Housekeeping successfully started"
+msgstr "已開始維護"
+
+msgid "Import repository"
+msgstr "導入存儲庫"
+
 msgid "Interval Pattern"
-msgstr ""
+msgstr "循環週期"
 
 msgid "Introducing Cycle Analytics"
 msgstr "週期分析簡介"
 
+msgid "LFSStatus|Disabled"
+msgstr "停用"
+
+msgid "LFSStatus|Enabled"
+msgstr "啟用"
+
 msgid "Last %d day"
 msgid_plural "Last %d days"
-msgstr[0] "最後 %d 天"
+msgstr[0] "最近 %d 天"
 
 msgid "Last Pipeline"
-msgstr ""
+msgstr "最新流水線"
+
+msgid "Last Update"
+msgstr "最後更新"
+
+msgid "Last commit"
+msgstr "最後提交"
+
+msgid "Learn more in the"
+msgstr "了解更多"
+
+msgid "Learn more in the|pipeline schedules documentation"
+msgstr "流水線計劃文檔"
+
+msgid "Leave group"
+msgstr "退出群組"
+
+msgid "Leave project"
+msgstr "退出項目"
 
 msgid "Limited to showing %d event at most"
 msgid_plural "Limited to showing %d events at most"
@@ -114,15 +444,45 @@ msgstr[0] "最多顯示 %d 個事件"
 msgid "Median"
 msgstr "中位數"
 
+msgid "MissingSSHKeyWarningLink|add an SSH key"
+msgstr "添加壹個 SSH 公鑰"
+
 msgid "New Issue"
 msgid_plural "New Issues"
-msgstr[0] "新議題"
+msgstr[0] "新建議題"
 
 msgid "New Pipeline Schedule"
-msgstr ""
+msgstr "創建流水線計劃"
+
+msgid "New branch"
+msgstr "新增分支"
+
+msgid "New directory"
+msgstr "新增目錄"
+
+msgid "New file"
+msgstr "新增文件"
+
+msgid "New issue"
+msgstr "新議題"
+
+msgid "New merge request"
+msgstr "新增合併請求"
+
+msgid "New schedule"
+msgstr "新增计划"
+
+msgid "New snippet"
+msgstr "新代碼片段"
+
+msgid "New tag"
+msgstr "新增標籤"
+
+msgid "No repository"
+msgstr "沒有存儲庫"
 
 msgid "No schedules"
-msgstr ""
+msgstr "沒有計劃"
 
 msgid "Not available"
 msgstr "不可用"
@@ -130,54 +490,185 @@ msgstr "不可用"
 msgid "Not enough data"
 msgstr "數據不足"
 
+msgid "Notification events"
+msgstr "通知事件"
+
+msgid "NotificationEvent|Close issue"
+msgstr "關閉議題"
+
+msgid "NotificationEvent|Close merge request"
+msgstr "關閉合併請求"
+
+msgid "NotificationEvent|Failed pipeline"
+msgstr "流水線失敗"
+
+msgid "NotificationEvent|Merge merge request"
+msgstr "合併請求被合併"
+
+msgid "NotificationEvent|New issue"
+msgstr "新增議題"
+
+msgid "NotificationEvent|New merge request"
+msgstr "新合併請求"
+
+msgid "NotificationEvent|New note"
+msgstr "新增評論"
+
+msgid "NotificationEvent|Reassign issue"
+msgstr "重新指派議題"
+
+msgid "NotificationEvent|Reassign merge request"
+msgstr "重新指派合併請求"
+
+msgid "NotificationEvent|Reopen issue"
+msgstr "重啟議題"
+
+msgid "NotificationEvent|Successful pipeline"
+msgstr "流水線成功完成"
+
+msgid "NotificationLevel|Custom"
+msgstr "自定義"
+
+msgid "NotificationLevel|Disabled"
+msgstr "停用"
+
+msgid "NotificationLevel|Global"
+msgstr "全局"
+
+msgid "NotificationLevel|On mention"
+msgstr "提及"
+
+msgid "NotificationLevel|Participate"
+msgstr "參與"
+
+msgid "NotificationLevel|Watch"
+msgstr "關注"
+
+msgid "OfSearchInADropdown|Filter"
+msgstr "篩選"
+
 msgid "OpenedNDaysAgo|Opened"
 msgstr "開始於"
 
+msgid "Options"
+msgstr "操作"
+
 msgid "Owner"
-msgstr ""
+msgstr "所有者"
+
+msgid "Pipeline"
+msgstr "流水線"
 
 msgid "Pipeline Health"
 msgstr "流水線健康指標"
 
 msgid "Pipeline Schedule"
-msgstr ""
+msgstr "流水線計劃"
 
 msgid "Pipeline Schedules"
-msgstr ""
+msgstr "流水線計劃"
 
 msgid "PipelineSchedules|Activated"
-msgstr ""
+msgstr "是否啟用"
 
 msgid "PipelineSchedules|Active"
-msgstr ""
+msgstr "已啟用"
 
 msgid "PipelineSchedules|All"
-msgstr ""
+msgstr "所有"
 
 msgid "PipelineSchedules|Inactive"
-msgstr ""
+msgstr "未啟用"
 
 msgid "PipelineSchedules|Next Run"
-msgstr ""
+msgstr "下次運行時間"
 
 msgid "PipelineSchedules|None"
-msgstr ""
+msgstr "無"
 
 msgid "PipelineSchedules|Provide a short description for this pipeline"
-msgstr ""
+msgstr "為此流水線提供簡短描述"
 
 msgid "PipelineSchedules|Take ownership"
-msgstr ""
+msgstr "取得所有者"
 
 msgid "PipelineSchedules|Target"
-msgstr ""
+msgstr "目標"
+
+msgid "PipelineSheduleIntervalPattern|Custom"
+msgstr "自定義"
+
+msgid "Pipeline|with stage"
+msgstr "於階段"
+
+msgid "Pipeline|with stages"
+msgstr "於階段"
+
+msgid "Project '%{project_name}' queued for deletion."
+msgstr "項目 '%{project_name}' 已進入刪除隊列。"
+
+msgid "Project '%{project_name}' was successfully created."
+msgstr "項目 '%{project_name}' 已創建成功。"
+
+msgid "Project '%{project_name}' was successfully updated."
+msgstr "項目 '%{project_name}' 已更新完成。"
+
+msgid "Project '%{project_name}' will be deleted."
+msgstr "項目 '%{project_name}' 將被刪除。"
+
+msgid "Project access must be granted explicitly to each user."
+msgstr "項目訪問權限必須明確授權給每個用戶。"
+
+msgid "Project export could not be deleted."
+msgstr "無法刪除項目導出。"
+
+msgid "Project export has been deleted."
+msgstr "項目導出已被刪除。"
+
+msgid ""
+"Project export link has expired. Please generate a new export from your "
+"project settings."
+msgstr "項目導出鏈接已過期。請從項目設置中重新生成項目導出。"
+
+msgid "Project export started. A download link will be sent by email."
+msgstr "項目導出已開始。下載鏈接將通過電子郵件發送。"
+
+msgid "Project home"
+msgstr "項目首頁"
+
+msgid "ProjectFeature|Disabled"
+msgstr "停用"
+
+msgid "ProjectFeature|Everyone with access"
+msgstr "任何人都可訪問"
+
+msgid "ProjectFeature|Only team members"
+msgstr "只限團隊成員"
+
+msgid "ProjectFileTree|Name"
+msgstr "名稱"
+
+msgid "ProjectLastActivity|Never"
+msgstr "從未"
 
 msgid "ProjectLifecycle|Stage"
-msgstr "項目生命週期"
+msgstr "階段"
+
+msgid "ProjectNetworkGraph|Graph"
+msgstr "分支圖"
 
 msgid "Read more"
 msgstr "了解更多"
 
+msgid "Readme"
+msgstr "自述文件"
+
+msgid "RefSwitcher|Branches"
+msgstr "分支"
+
+msgid "RefSwitcher|Tags"
+msgstr "標籤"
+
 msgid "Related Commits"
 msgstr "相關的提交"
 
@@ -194,59 +685,164 @@ msgid "Related Merge Requests"
 msgstr "相關的合併請求"
 
 msgid "Related Merged Requests"
-msgstr "相關已合併的合並請求"
+msgstr "相關已合併的合併請求"
+
+msgid "Remind later"
+msgstr "稍後提醒"
+
+msgid "Remove project"
+msgstr "刪除項目"
+
+msgid "Request Access"
+msgstr "申請權限"
+
+msgid "Revert this commit"
+msgstr "還原此提交"
+
+msgid "Revert this merge request"
+msgstr "還原此合併請求"
 
 msgid "Save pipeline schedule"
-msgstr ""
+msgstr "保存流水線計劃"
 
 msgid "Schedule a new pipeline"
-msgstr ""
+msgstr "新建流水線計劃"
+
+msgid "Scheduling Pipelines"
+msgstr "流水線計劃"
+
+msgid "Search branches and tags"
+msgstr "搜索分支和標籤"
+
+msgid "Select Archive Format"
+msgstr "選擇下載格式"
 
 msgid "Select a timezone"
-msgstr ""
+msgstr "選擇時區"
 
 msgid "Select target branch"
-msgstr ""
+msgstr "選擇目標分支"
+
+msgid "Set a password on your account to pull or push via %{protocol}."
+msgstr "為賬號添加壹個用於推送或拉取的 %{protocol} 密碼。"
+
+msgid "Set up CI"
+msgstr "設置 CI"
+
+msgid "Set up Koding"
+msgstr "設置 Koding"
+
+msgid "Set up auto deploy"
+msgstr "設置自動部署"
+
+msgid "SetPasswordToCloneLink|set a password"
+msgstr "設置密碼"
 
 msgid "Showing %d event"
 msgid_plural "Showing %d events"
 msgstr[0] "顯示 %d 個事件"
 
+msgid "Source code"
+msgstr "源代碼"
+
+msgid "StarProject|Star"
+msgstr "星標"
+
+msgid "Start a %{new_merge_request} with these changes"
+msgstr "由此更改 %{new_merge_request}"
+
+msgid "Switch branch/tag"
+msgstr "切換分支/標籤"
+
+msgid "Tag"
+msgid_plural "Tags"
+msgstr[0] "標籤"
+
+msgid "Tags"
+msgstr "標籤"
+
 msgid "Target Branch"
-msgstr ""
+msgstr "目標分支"
 
-msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request."
-msgstr "編碼階段概述了從第一次提交到創建合併請求的時間。創建第壹個合並請求後,數據將自動添加到此處。"
+msgid ""
+"The coding stage shows the time from the first commit to creating the merge "
+"request. The data will automatically be added here once you create your "
+"first merge request."
+msgstr "編碼階段概述了從第壹次提交到創建合併請求的時間。創建第壹個合併請求後,數據將自動添加到此處。"
 
 msgid "The collection of events added to the data gathered for that stage."
 msgstr "與該階段相關的事件。"
 
-msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage."
-msgstr "議題階段概述了從創建議題到將議題設置裏程碑或將議題添加到議題看板的時間。創建一個議題後,數據將自動添加到此處。"
+msgid "The fork relationship has been removed."
+msgstr "派生關係已被刪除。"
+
+msgid ""
+"The issue stage shows the time it takes from creating an issue to assigning "
+"the issue to a milestone, or add the issue to a list on your Issue Board. "
+"Begin creating issues to see data for this stage."
+msgstr "議題階段概述了從創建議題到將議題添加到裏程碑或議題看板所花費的時間。創建第壹個議題後,數據將自動添加到此處.。"
 
 msgid "The phase of the development lifecycle."
 msgstr "項目生命週期中的各個階段。"
 
-msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit."
-msgstr "計劃階段概述了從議題添加到日程後到推送首次提交的時間。當首次推送提交後,數據將自動添加到此處。"
+msgid ""
+"The pipelines schedule runs pipelines in the future, repeatedly, for "
+"specific branches or tags. Those scheduled pipelines will inherit limited "
+"project access based on their associated user."
+msgstr "流水線計劃會週期性重複運行指定分支或標籤的流水線。這些流水線將根據其關聯用戶繼承有限的項目訪問權限。"
+
+msgid ""
+"The planning stage shows the time from the previous step to pushing your "
+"first commit. This time will be added automatically once you push your first "
+"commit."
+msgstr "計劃階段概述了從議題添加到日程到推送首次提交的時間。當首次推送提交後,數據將自動添加到此處。"
 
-msgid "The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
+msgid ""
+"The production stage shows the total time it takes between creating an issue "
+"and deploying the code to production. The data will be automatically added "
+"once you have completed the full idea to production cycle."
 msgstr "生產階段概述了從創建議題到將代碼部署到生產環境的時間。當完成完整的想法到部署生產,數據將自動添加到此處。"
 
-msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request."
-msgstr "評審階段概述了從創建合並請求到合併的時間。當創建第壹個合並請求後,數據將自動添加到此處。"
+msgid "The project can be accessed by any logged in user."
+msgstr "該項目允許已登錄的用戶訪問。"
 
-msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time."
-msgstr "預發布階段概述了合並請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。"
+msgid "The project can be accessed without any authentication."
+msgstr "該項目允許任何人訪問。"
 
-msgid "The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running."
-msgstr "測試階段概述了GitLab CI為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。"
+msgid "The repository for this project does not exist."
+msgstr "此項目的存儲庫不存在。"
+
+msgid ""
+"The review stage shows the time from creating the merge request to merging "
+"it. The data will automatically be added after you merge your first merge "
+"request."
+msgstr "評審階段概述了從創建合併請求到合併的時間。當創建第壹個合併請求後,數據將自動添加到此處。"
+
+msgid ""
+"The staging stage shows the time between merging the MR and deploying code "
+"to the production environment. The data will be automatically added once you "
+"deploy to production for the first time."
+msgstr "預發布階段概述了合併請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。"
+
+msgid ""
+"The testing stage shows the time GitLab CI takes to run every pipeline for "
+"the related merge request. The data will automatically be added after your "
+"first pipeline finishes running."
+msgstr "測試階段概述了 GitLab CI 為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。"
 
 msgid "The time taken by each data entry gathered by that stage."
 msgstr "該階段每條數據所花的時間"
 
-msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
-msgstr "中位數是一個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。"
+msgid ""
+"The value lying at the midpoint of a series of observed values. E.g., "
+"between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 ="
+" 6."
+msgstr "中位數是壹個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。"
+
+msgid ""
+"This means you can not push code until you create an empty repository or "
+"import existing one."
+msgstr "在創建壹個空的存儲庫或導入現有存儲庫之前,您將無法推送代碼。"
 
 msgid "Time before an issue gets scheduled"
 msgstr "議題被列入日程表的時間"
@@ -255,11 +851,134 @@ msgid "Time before an issue starts implementation"
 msgstr "開始進行編碼前的時間"
 
 msgid "Time between merge request creation and merge/close"
-msgstr "從創建合併請求到被合並或關閉的時間"
+msgstr "從創建合併請求到被合併或關閉的時間"
 
 msgid "Time until first merge request"
 msgstr "創建第壹個合併請求之前的時間"
 
+msgid "Timeago|%s days ago"
+msgstr " %s 天前"
+
+msgid "Timeago|%s days remaining"
+msgstr "剩餘 %s 天"
+
+msgid "Timeago|%s hours remaining"
+msgstr "剩餘 %s 小時"
+
+msgid "Timeago|%s minutes ago"
+msgstr " %s 分鐘前"
+
+msgid "Timeago|%s minutes remaining"
+msgstr "剩餘 %s 分鐘"
+
+msgid "Timeago|%s months ago"
+msgstr " %s 個月前"
+
+msgid "Timeago|%s months remaining"
+msgstr "剩餘 %s 月"
+
+msgid "Timeago|%s seconds remaining"
+msgstr "剩餘 %s 秒"
+
+msgid "Timeago|%s weeks ago"
+msgstr " %s 星期前"
+
+msgid "Timeago|%s weeks remaining"
+msgstr "剩餘 %s 星期"
+
+msgid "Timeago|%s years ago"
+msgstr " %s 年前"
+
+msgid "Timeago|%s years remaining"
+msgstr "剩餘 %s 年"
+
+msgid "Timeago|1 day remaining"
+msgstr "剩餘 1 天"
+
+msgid "Timeago|1 hour remaining"
+msgstr "剩餘 1 小時"
+
+msgid "Timeago|1 minute remaining"
+msgstr "剩餘 1 分鐘"
+
+msgid "Timeago|1 month remaining"
+msgstr "剩餘 1 個月"
+
+msgid "Timeago|1 week remaining"
+msgstr "剩餘 1 星期"
+
+msgid "Timeago|1 year remaining"
+msgstr "剩餘 1 年"
+
+msgid "Timeago|Past due"
+msgstr "逾期"
+
+msgid "Timeago|a day ago"
+msgstr " 1 天前"
+
+msgid "Timeago|a month ago"
+msgstr " 1 個月前"
+
+msgid "Timeago|a week ago"
+msgstr " 1 星期前"
+
+msgid "Timeago|a while"
+msgstr " 剛剛"
+
+msgid "Timeago|a year ago"
+msgstr " 1 年前"
+
+msgid "Timeago|about %s hours ago"
+msgstr "約 %s 小時前"
+
+msgid "Timeago|about a minute ago"
+msgstr "約 1 分鐘前"
+
+msgid "Timeago|about an hour ago"
+msgstr "約 1 小時前"
+
+msgid "Timeago|in %s days"
+msgstr " %s 天後"
+
+msgid "Timeago|in %s hours"
+msgstr " %s 小時後"
+
+msgid "Timeago|in %s minutes"
+msgstr " %s 分鐘後"
+
+msgid "Timeago|in %s months"
+msgstr " %s 個月後"
+
+msgid "Timeago|in %s seconds"
+msgstr " %s 秒後"
+
+msgid "Timeago|in %s weeks"
+msgstr " %s 星期後"
+
+msgid "Timeago|in %s years"
+msgstr " %s 年後"
+
+msgid "Timeago|in 1 day"
+msgstr " 1 天後"
+
+msgid "Timeago|in 1 hour"
+msgstr " 1 小時後"
+
+msgid "Timeago|in 1 minute"
+msgstr " 1 分鐘後"
+
+msgid "Timeago|in 1 month"
+msgstr " 1 月後"
+
+msgid "Timeago|in 1 week"
+msgstr " 1 星期後"
+
+msgid "Timeago|in 1 year"
+msgstr " 1 年後"
+
+msgid "Timeago|less than a minute ago"
+msgstr "不到 1 分鐘前"
+
 msgid "Time|hr"
 msgid_plural "Time|hrs"
 msgstr[0] "小時"
@@ -277,15 +996,114 @@ msgstr "總時間"
 msgid "Total test time for all commits/merges"
 msgstr "所有提交和合併的總測試時間"
 
+msgid "Unstar"
+msgstr "取消星標"
+
+msgid "Upload New File"
+msgstr "上傳新文件"
+
+msgid "Upload file"
+msgstr "上傳文件"
+
+msgid "UploadLink|click to upload"
+msgstr "點擊上傳"
+
+msgid "Use your global notification setting"
+msgstr "使用全局通知設置"
+
+msgid "View open merge request"
+msgstr "查看開啟的合並請求"
+
+msgid "VisibilityLevel|Internal"
+msgstr "內部"
+
+msgid "VisibilityLevel|Private"
+msgstr "私有"
+
+msgid "VisibilityLevel|Public"
+msgstr "公開"
+
 msgid "Want to see the data? Please ask an administrator for access."
 msgstr "權限不足。如需查看相關數據,請向管理員申請權限。"
 
 msgid "We don't have enough data to show this stage."
 msgstr "該階段的數據不足,無法顯示。"
 
+msgid "Withdraw Access Request"
+msgstr "取消權限申请"
+
+msgid ""
+"You are going to remove %{project_name_with_namespace}.\n"
+"Removed project CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr "即將要刪除 %{project_name_with_namespace}。\n"
+"已刪除的項目無法恢複!\n"
+"確定繼續嗎?"
+
+msgid ""
+"You are going to remove the fork relationship to source project "
+"%{forked_from_project}. Are you ABSOLUTELY sure?"
+msgstr "即將刪除與源項目 %{forked_from_project} 的派生關系。確定繼續嗎?"
+
+msgid ""
+"You are going to transfer %{project_name_with_namespace} to another owner. "
+"Are you ABSOLUTELY sure?"
+msgstr "即將 %{project_name_with_namespace} 轉義給另壹個所有者。確定繼續嗎?"
+
+msgid "You can only add files when you are on a branch"
+msgstr "只能在分支上添加文件"
+
+msgid "You have reached your project limit"
+msgstr "您已達到項目數量限制"
+
+msgid "You must sign in to star a project"
+msgstr "必須登錄才能對項目加星標"
+
 msgid "You need permission."
-msgstr "您需要相關的權限。"
+msgstr "需要相關的權限。"
+
+msgid "You will not get any notifications via email"
+msgstr "不會收到任何通知郵件"
+
+msgid "You will only receive notifications for the events you choose"
+msgstr "只接收您選擇的事件通知"
+
+msgid ""
+"You will only receive notifications for threads you have participated in"
+msgstr "只接收您參與的主題的通知"
+
+msgid "You will receive notifications for any activity"
+msgstr "接收所有活動的通知"
+
+msgid ""
+"You will receive notifications only for comments in which you were "
+"@mentioned"
+msgstr "只接收評論中提及(@)您的通知"
+
+msgid ""
+"You won't be able to pull or push project code via %{protocol} until you "
+"%{set_password_link} on your account"
+msgstr "在賬號上  %{set_password_link} 之前將無法通過 %{protocol} 拉取或推送代碼。"
+
+msgid ""
+"You won't be able to pull or push project code via SSH until you "
+"%{add_ssh_key_link} to your profile"
+msgstr "在賬號中 %{add_ssh_key_link} 之前將無法通過 SSH 拉取或推送代碼。"
+
+msgid "Your name"
+msgstr "您的名字"
 
 msgid "day"
 msgid_plural "days"
 msgstr[0] "天"
+
+msgid "new merge request"
+msgstr "新建合併請求"
+
+msgid "notification emails"
+msgstr "通知郵件"
+
+msgid "parent"
+msgid_plural "parents"
+msgstr[0] "父級"
+
diff --git a/locale/zh_TW/gitlab.po b/locale/zh_TW/gitlab.po
index e40723a9d8d462573bc56275e3c8b6d6f17cc032..91cac543a25b67a4e5866f8630216c143b6407b8 100644
--- a/locale/zh_TW/gitlab.po
+++ b/locale/zh_TW/gitlab.po
@@ -1,128 +1,460 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the gitlab package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
-#
+# Huang Tao <htve@outlook.com>, 2017. #zanata
+# Lin Jen-Shin <anonymous@domain.com>, 2017.
+# Hazel Yang <anonymous@domain.com>, 2017.
+# TzeKei Lee <anonymous@domain.com>, 2017.
+# Jerry Ho <a29988122@gmail.com>, 2017.
 msgid ""
 msgstr ""
 "Project-Id-Version: gitlab 1.0.0\n"
 "Report-Msgid-Bugs-To: \n"
-"PO-Revision-Date: 2017-05-04 19:24-0500\n"
-"Last-Translator: HuangTao <htve@outlook.com>, 2017\n"
-"Language-Team: Chinese (Taiwan) (https://www.transifex.com/gitlab-zh/teams/751"
-"77/zh_TW/)\n"
+"POT-Creation-Date: 2017-06-15 21:59-0500\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
-"Language: zh_TW\n"
-"Plural-Forms: nplurals=1; plural=0;\n"
+"PO-Revision-Date: 2017-06-28 11:13-0400\n"
+"Last-Translator: Huang Tao <htve@outlook.com>\n"
+"Language-Team: Chinese (Taiwan) (https://translate.zanata.org/project/view/GitLab)\n"
+"Language: zh-TW\n"
+"X-Generator: Zanata 3.9.6\n"
+"Plural-Forms: nplurals=1; plural=0\n"
+
+msgid "%{commit_author_link} committed %{commit_timeago}"
+msgstr "%{commit_author_link} 在 %{commit_timeago} 送交"
+
+msgid "About auto deploy"
+msgstr "關於自動部署"
+
+msgid "Active"
+msgstr "啟用"
+
+msgid "Activity"
+msgstr "活動"
+
+msgid "Add Changelog"
+msgstr "新增更新日誌"
+
+msgid "Add Contribution guide"
+msgstr "新增協作指南"
+
+msgid "Add License"
+msgstr "新增授權條款"
+
+msgid "Add an SSH key to your profile to pull or push via SSH."
+msgstr "請先新增 SSH 金鑰到您的個人帳號,才能使用 SSH 來上傳 (push) 或下載 (pull) 。"
+
+msgid "Add new directory"
+msgstr "新增目錄"
+
+msgid "Archived project! Repository is read-only"
+msgstr "此專案已封存!檔案庫 (repository) 為唯讀狀態"
 
 msgid "Are you sure you want to delete this pipeline schedule?"
+msgstr "確定要刪除此流水線 (pipeline) 排程嗎?"
+
+msgid "Attach a file by drag &amp; drop or %{upload_link}"
+msgstr "拖放檔案到此處或者 %{upload_link}"
+
+msgid "Branch"
+msgid_plural "Branches"
+msgstr[0] "分支 (branch) "
+
+msgid ""
+"Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, "
+"choose a GitLab CI Yaml template and commit your changes. "
+"%{link_to_autodeploy_doc}"
 msgstr ""
+"已建立分支 (branch)  <strong>%{branch_name}</strong> 。如要設定自動部署, 請選擇合適的 GitLab CI "
+"Yaml 模板,然後記得要送交 (commit) 您的編輯內容。%{link_to_autodeploy_doc}\n"
+
+msgid "Branches"
+msgstr "分支 (branch) "
+
+msgid "Browse files"
+msgstr "瀏覽檔案"
 
 msgid "ByAuthor|by"
-msgstr "作者:"
+msgstr "作者:"
+
+msgid "CI configuration"
+msgstr "CI 組態"
 
 msgid "Cancel"
-msgstr ""
+msgstr "取消"
+
+msgid "ChangeTypeActionLabel|Pick into branch"
+msgstr "挑選到分支 (branch) "
+
+msgid "ChangeTypeActionLabel|Revert in branch"
+msgstr "還原分支 (branch) "
+
+msgid "ChangeTypeAction|Cherry-pick"
+msgstr "挑選"
+
+msgid "ChangeTypeAction|Revert"
+msgstr "還原"
+
+msgid "Changelog"
+msgstr "更新日誌"
+
+msgid "Charts"
+msgstr "統計圖"
+
+msgid "Cherry-pick this commit"
+msgstr "挑選此更動記錄 (commit) "
+
+msgid "Cherry-pick this merge request"
+msgstr "挑選此合併請求 (merge request) "
+
+msgid "CiStatusLabel|canceled"
+msgstr "已取消"
+
+msgid "CiStatusLabel|created"
+msgstr "已建立"
+
+msgid "CiStatusLabel|failed"
+msgstr "失敗"
+
+msgid "CiStatusLabel|manual action"
+msgstr "手動操作"
+
+msgid "CiStatusLabel|passed"
+msgstr "已通過"
+
+msgid "CiStatusLabel|passed with warnings"
+msgstr "通過,但有警告訊息"
+
+msgid "CiStatusLabel|pending"
+msgstr "等待中"
+
+msgid "CiStatusLabel|skipped"
+msgstr "已跳過"
+
+msgid "CiStatusLabel|waiting for manual action"
+msgstr "等待手動操作"
+
+msgid "CiStatusText|blocked"
+msgstr "已阻擋"
+
+msgid "CiStatusText|canceled"
+msgstr "已取消"
+
+msgid "CiStatusText|created"
+msgstr "已建立"
+
+msgid "CiStatusText|failed"
+msgstr "失敗"
+
+msgid "CiStatusText|manual"
+msgstr "手動操作"
+
+msgid "CiStatusText|passed"
+msgstr "已通過"
+
+msgid "CiStatusText|pending"
+msgstr "等待中"
+
+msgid "CiStatusText|skipped"
+msgstr "已跳過"
+
+msgid "CiStatus|running"
+msgstr "執行中"
 
 msgid "Commit"
 msgid_plural "Commits"
-msgstr[0] "送交"
+msgstr[0] "更動記錄 (commit) "
+
+msgid "Commit message"
+msgstr "更動說明 (commit) "
+
+msgid "CommitBoxTitle|Commit"
+msgstr "送交"
+
+msgid "CommitMessage|Add %{file_name}"
+msgstr "建立 %{file_name}"
+
+msgid "Commits"
+msgstr "更動記錄 (commit) "
+
+msgid "Commits|History"
+msgstr "過去更動 (commit) "
+
+msgid "Committed by"
+msgstr "送交者為 "
+
+msgid "Compare"
+msgstr "比較"
+
+msgid "Contribution guide"
+msgstr "協作指南"
+
+msgid "Contributors"
+msgstr "協作者"
+
+msgid "Copy URL to clipboard"
+msgstr "複製網址到剪貼簿"
+
+msgid "Copy commit SHA to clipboard"
+msgstr "複製更動記錄 (commit) 的 SHA 值到剪貼簿"
+
+msgid "Create New Directory"
+msgstr "建立新目錄"
+
+msgid "Create directory"
+msgstr "建立目錄"
+
+msgid "Create empty bare repository"
+msgstr "建立一個新的 bare repository"
+
+msgid "Create merge request"
+msgstr "發出合併請求 (merge request) "
+
+msgid "Create new..."
+msgstr "建立..."
+
+msgid "CreateNewFork|Fork"
+msgstr "分支 (fork) "
+
+msgid "CreateTag|Tag"
+msgstr "建立標籤"
 
 msgid "Cron Timezone"
+msgstr "Cron 時區"
+
+msgid "Cron syntax"
+msgstr "Cron 語法"
+
+msgid "Custom notification events"
+msgstr "自訂事件通知"
+
+msgid ""
+"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 %{notification_link}."
 msgstr ""
+"自訂通知層級相當於參與度設定。使用自訂通知層級,您可以只收到特定的事件通知。請參照 %{notification_link} 以獲得更多訊息。"
 
-msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project."
-msgstr "週期分析概述了你的專案從想法到產品實現,各階段所需的時間。"
+msgid "Cycle Analytics"
+msgstr "週期分析"
+
+msgid ""
+"Cycle Analytics gives an overview of how much time it takes to go from idea "
+"to production in your project."
+msgstr "週期分析讓您可以有效的釐清專案從發想到產品推出所花的時間長短。"
 
 msgid "CycleAnalyticsStage|Code"
 msgstr "程式開發"
 
 msgid "CycleAnalyticsStage|Issue"
-msgstr "議題"
+msgstr "議題 (issue) "
 
 msgid "CycleAnalyticsStage|Plan"
 msgstr "計劃"
 
 msgid "CycleAnalyticsStage|Production"
-msgstr "上線"
+msgstr "營運"
 
 msgid "CycleAnalyticsStage|Review"
 msgstr "複閱"
 
 msgid "CycleAnalyticsStage|Staging"
-msgstr "預備"
+msgstr "試營運"
 
 msgid "CycleAnalyticsStage|Test"
 msgstr "測試"
 
+msgid "Define a custom pattern with cron syntax"
+msgstr "使用 Cron 語法自訂排程"
+
 msgid "Delete"
-msgstr ""
+msgstr "刪除"
 
 msgid "Deploy"
 msgid_plural "Deploys"
 msgstr[0] "部署"
 
 msgid "Description"
-msgstr ""
+msgstr "描述"
+
+msgid "Directory name"
+msgstr "目錄名稱"
+
+msgid "Don't show again"
+msgstr "不再顯示"
+
+msgid "Download"
+msgstr "下載"
+
+msgid "Download tar"
+msgstr "下載 tar"
+
+msgid "Download tar.bz2"
+msgstr "下載 tar.bz2"
+
+msgid "Download tar.gz"
+msgstr "下載 tar.gz"
+
+msgid "Download zip"
+msgstr "下載 zip"
+
+msgid "DownloadArtifacts|Download"
+msgstr "下載"
+
+msgid "DownloadCommit|Email Patches"
+msgstr "電子郵件修補檔案 (patch)"
+
+msgid "DownloadCommit|Plain Diff"
+msgstr "差異檔 (diff)"
+
+msgid "DownloadSource|Download"
+msgstr "下載原始碼"
 
 msgid "Edit"
-msgstr ""
+msgstr "編輯"
 
 msgid "Edit Pipeline Schedule %{id}"
-msgstr ""
+msgstr "編輯 %{id} 流水線 (pipeline) 排程"
+
+msgid "Every day (at 4:00am)"
+msgstr "每日執行(淩晨四點)"
+
+msgid "Every month (on the 1st at 4:00am)"
+msgstr "每月執行(每月一日淩晨四點)"
+
+msgid "Every week (Sundays at 4:00am)"
+msgstr "每週執行(週日淩晨 四點)"
 
 msgid "Failed to change the owner"
-msgstr ""
+msgstr "無法變更所有權"
 
 msgid "Failed to remove the pipeline schedule"
-msgstr ""
+msgstr "無法刪除流水線 (pipeline) 排程"
 
-msgid "Filter"
-msgstr ""
+msgid "Files"
+msgstr "檔案"
+
+msgid "Find by path"
+msgstr "以路徑搜尋"
+
+msgid "Find file"
+msgstr "搜尋檔案"
 
 msgid "FirstPushedBy|First"
-msgstr "首次推送"
+msgstr "首次推送 (push) "
 
 msgid "FirstPushedBy|pushed by"
-msgstr "推送者:"
+msgstr "推送者 (push) :"
+
+msgid "Fork"
+msgid_plural "Forks"
+msgstr[0] "分支 (fork) "
+
+msgid "ForkedFromProjectPath|Forked from"
+msgstr "分支 (fork) 自"
 
 msgid "From issue creation until deploy to production"
-msgstr "從議題建立至線上部署"
+msgstr "從議題 (issue) 建立直到部署至營運環境"
 
 msgid "From merge request merge until deploy to production"
-msgstr "從請求被合併後至線上部署"
+msgstr "從請求被合併後 (merge request merged) 直到部署至營運環境"
+
+msgid "Go to your fork"
+msgstr "前往您的分支 (fork) "
+
+msgid "GoToYourFork|Fork"
+msgstr "前往您的分支 (fork) "
+
+msgid "Home"
+msgstr "首頁"
+
+msgid "Housekeeping successfully started"
+msgstr "已開始維護"
+
+msgid "Import repository"
+msgstr "匯入檔案庫 (repository)"
 
 msgid "Interval Pattern"
-msgstr ""
+msgstr "循環週期"
 
 msgid "Introducing Cycle Analytics"
 msgstr "週期分析簡介"
 
+msgid "LFSStatus|Disabled"
+msgstr "停用"
+
+msgid "LFSStatus|Enabled"
+msgstr "啟用"
+
 msgid "Last %d day"
 msgid_plural "Last %d days"
-msgstr[0] "最後 %d 天"
+msgstr[0] "最近 %d 天"
 
 msgid "Last Pipeline"
-msgstr ""
+msgstr "最新流水線 (pipeline) "
+
+msgid "Last Update"
+msgstr "最後更新"
+
+msgid "Last commit"
+msgstr "最後更動記錄 (commit) "
+
+msgid "Learn more in the"
+msgstr "了解更多"
+
+msgid "Learn more in the|pipeline schedules documentation"
+msgstr "流水線 (pipeline) 排程說明文件"
+
+msgid "Leave group"
+msgstr "退出群組"
+
+msgid "Leave project"
+msgstr "退出專案"
 
 msgid "Limited to showing %d event at most"
 msgid_plural "Limited to showing %d events at most"
-msgstr[0] "最多顯示 %d 個事件"
+msgstr[0] "限制最多顯示 %d 個事件"
 
 msgid "Median"
 msgstr "中位數"
 
+msgid "MissingSSHKeyWarningLink|add an SSH key"
+msgstr "新增 SSH 金鑰"
+
 msgid "New Issue"
 msgid_plural "New Issues"
-msgstr[0] "新議題"
+msgstr[0] "建立議題 (issue) "
 
 msgid "New Pipeline Schedule"
-msgstr ""
+msgstr "建立流水線 (pipeline) 排程"
+
+msgid "New branch"
+msgstr "新分支 (branch) "
+
+msgid "New directory"
+msgstr "新增目錄"
+
+msgid "New file"
+msgstr "新增檔案"
+
+msgid "New issue"
+msgstr "新增議題 (issue) "
+
+msgid "New merge request"
+msgstr "新增合併請求 (merge request) "
+
+msgid "New schedule"
+msgstr "新增排程"
+
+msgid "New snippet"
+msgstr "新文字片段"
+
+msgid "New tag"
+msgstr "新增標籤"
+
+msgid "No repository"
+msgstr "找不到檔案庫 (repository)"
 
 msgid "No schedules"
-msgstr ""
+msgstr "沒有排程"
 
 msgid "Not available"
 msgstr "無法使用"
@@ -130,135 +462,502 @@ msgstr "無法使用"
 msgid "Not enough data"
 msgstr "資料不足"
 
+msgid "Notification events"
+msgstr "事件通知"
+
+msgid "NotificationEvent|Close issue"
+msgstr "關閉議題 (issue) "
+
+msgid "NotificationEvent|Close merge request"
+msgstr "關閉合併請求 (merge request) "
+
+msgid "NotificationEvent|Failed pipeline"
+msgstr "流水線 (pipeline) 失敗"
+
+msgid "NotificationEvent|Merge merge request"
+msgstr "合併請求 (merge request) 被合併"
+
+msgid "NotificationEvent|New issue"
+msgstr "新增議題 (issue) "
+
+msgid "NotificationEvent|New merge request"
+msgstr "新增合併請求 (merge request) "
+
+msgid "NotificationEvent|New note"
+msgstr "新增評論"
+
+msgid "NotificationEvent|Reassign issue"
+msgstr "重新指派議題 (issue) "
+
+msgid "NotificationEvent|Reassign merge request"
+msgstr "重新指派合併請求 (merge request) "
+
+msgid "NotificationEvent|Reopen issue"
+msgstr "重啟議題 (issue)"
+
+msgid "NotificationEvent|Successful pipeline"
+msgstr "流水線 (pipeline) 成功完成"
+
+msgid "NotificationLevel|Custom"
+msgstr "自訂"
+
+msgid "NotificationLevel|Disabled"
+msgstr "停用"
+
+msgid "NotificationLevel|Global"
+msgstr "全域"
+
+msgid "NotificationLevel|On mention"
+msgstr "提及"
+
+msgid "NotificationLevel|Participate"
+msgstr "參與"
+
+msgid "NotificationLevel|Watch"
+msgstr "關注"
+
+msgid "OfSearchInADropdown|Filter"
+msgstr "篩選"
+
 msgid "OpenedNDaysAgo|Opened"
 msgstr "開始於"
 
+msgid "Options"
+msgstr "選項"
+
 msgid "Owner"
-msgstr ""
+msgstr "所有權"
+
+msgid "Pipeline"
+msgstr "流水線 (pipeline) "
 
 msgid "Pipeline Health"
-msgstr "流水線健康指標"
+msgstr "流水線 (pipeline) 健康指數"
 
 msgid "Pipeline Schedule"
-msgstr ""
+msgstr "流水線 (pipeline) 排程"
 
 msgid "Pipeline Schedules"
-msgstr ""
+msgstr "流水線 (pipeline) 排程"
 
 msgid "PipelineSchedules|Activated"
-msgstr ""
+msgstr "是否啟用"
 
 msgid "PipelineSchedules|Active"
-msgstr ""
+msgstr "已啟用"
 
 msgid "PipelineSchedules|All"
-msgstr ""
+msgstr "所有"
 
 msgid "PipelineSchedules|Inactive"
-msgstr ""
+msgstr "未啟用"
 
 msgid "PipelineSchedules|Next Run"
-msgstr ""
+msgstr "下次執行時間"
 
 msgid "PipelineSchedules|None"
-msgstr ""
+msgstr "無"
 
 msgid "PipelineSchedules|Provide a short description for this pipeline"
-msgstr ""
+msgstr "請簡單說明此流水線 (pipeline) "
 
 msgid "PipelineSchedules|Take ownership"
-msgstr ""
+msgstr "取得所有權"
 
 msgid "PipelineSchedules|Target"
-msgstr ""
+msgstr "目標"
+
+msgid "PipelineSheduleIntervalPattern|Custom"
+msgstr "自訂"
+
+msgid "Pipeline|with stage"
+msgstr "於階段"
+
+msgid "Pipeline|with stages"
+msgstr "於階段"
+
+msgid "Project '%{project_name}' queued for deletion."
+msgstr "專案 '%{project_name}' 已加入刪除佇列。"
+
+msgid "Project '%{project_name}' was successfully created."
+msgstr "專案 '%{project_name}' 建立完成。"
+
+msgid "Project '%{project_name}' was successfully updated."
+msgstr "專案 '%{project_name}' 更新完成。"
+
+msgid "Project '%{project_name}' will be deleted."
+msgstr "專案 '%{project_name}' 將被刪除。"
+
+msgid "Project access must be granted explicitly to each user."
+msgstr "專案權限必須一一指派給每個使用者。"
+
+msgid "Project export could not be deleted."
+msgstr "匯出的專案無法被刪除。"
+
+msgid "Project export has been deleted."
+msgstr "匯出的專案已被刪除。"
+
+msgid ""
+"Project export link has expired. Please generate a new export from your "
+"project settings."
+msgstr "專案的匯出連結已失效。請到專案設定中產生新的連結。"
+
+msgid "Project export started. A download link will be sent by email."
+msgstr "專案導出已開始。完成後下載連結會送到您的信箱。"
+
+msgid "Project home"
+msgstr "專案首頁"
+
+msgid "ProjectFeature|Disabled"
+msgstr "停用"
+
+msgid "ProjectFeature|Everyone with access"
+msgstr "任何人都可存取"
+
+msgid "ProjectFeature|Only team members"
+msgstr "只有團隊成員可以存取"
+
+msgid "ProjectFileTree|Name"
+msgstr "名稱"
+
+msgid "ProjectLastActivity|Never"
+msgstr "從未"
 
 msgid "ProjectLifecycle|Stage"
-msgstr "專案生命週期"
+msgstr "階段"
+
+msgid "ProjectNetworkGraph|Graph"
+msgstr "分支圖"
 
 msgid "Read more"
-msgstr "了解更多"
+msgstr "瞭解更多"
+
+msgid "Readme"
+msgstr "說明檔"
+
+msgid "RefSwitcher|Branches"
+msgstr "分支 (branch) "
+
+msgid "RefSwitcher|Tags"
+msgstr "標籤"
 
 msgid "Related Commits"
-msgstr "相關的送交"
+msgstr "相關的更動記錄 (commit) "
 
 msgid "Related Deployed Jobs"
 msgstr "相關的部署作業"
 
 msgid "Related Issues"
-msgstr "相關的議題"
+msgstr "相關的議題 (issue) "
 
 msgid "Related Jobs"
 msgstr "相關的作業"
 
 msgid "Related Merge Requests"
-msgstr "相關的合併請求"
+msgstr "相關的合併請求 (merge request) "
 
 msgid "Related Merged Requests"
 msgstr "相關已合併的請求"
 
+msgid "Remind later"
+msgstr "稍後提醒"
+
+msgid "Remove project"
+msgstr "刪除專案"
+
+msgid "Request Access"
+msgstr "申請權限"
+
+msgid "Revert this commit"
+msgstr "還原此更動記錄 (commit)"
+
+msgid "Revert this merge request"
+msgstr "還原此合併請求 (merge request) "
+
 msgid "Save pipeline schedule"
-msgstr ""
+msgstr "保存流水線 (pipeline) 排程"
 
 msgid "Schedule a new pipeline"
-msgstr ""
+msgstr "建立流水線 (pipeline) 排程"
+
+msgid "Scheduling Pipelines"
+msgstr "流水線 (pipeline) 計劃"
+
+msgid "Search branches and tags"
+msgstr "搜尋分支 (branch) 和標籤"
+
+msgid "Select Archive Format"
+msgstr "選擇下載格式"
 
 msgid "Select a timezone"
-msgstr ""
+msgstr "選擇時區"
 
 msgid "Select target branch"
-msgstr ""
+msgstr "選擇目標分支 (branch) "
+
+msgid "Set a password on your account to pull or push via %{protocol}."
+msgstr "請先設定密碼,才能使用 %{protocol} 來上傳 (push) 或下載 (pull) 。"
+
+msgid "Set up CI"
+msgstr "設定 CI"
+
+msgid "Set up Koding"
+msgstr "設定 Koding"
+
+msgid "Set up auto deploy"
+msgstr "設定自動部署"
+
+msgid "SetPasswordToCloneLink|set a password"
+msgstr "設定密碼"
 
 msgid "Showing %d event"
 msgid_plural "Showing %d events"
 msgstr[0] "顯示 %d 個事件"
 
+msgid "Source code"
+msgstr "原始碼"
+
+msgid "StarProject|Star"
+msgstr "收藏"
+
+msgid "Start a %{new_merge_request} with these changes"
+msgstr "以這些改動建立一個新的 %{new_merge_request} "
+
+msgid "Switch branch/tag"
+msgstr "切換分支 (branch) 或標籤"
+
+msgid "Tag"
+msgid_plural "Tags"
+msgstr[0] "標籤"
+
+msgid "Tags"
+msgstr "標籤"
+
 msgid "Target Branch"
-msgstr ""
+msgstr "目標分支 (branch) "
 
-msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request."
-msgstr "程式開發階段顯示從第一次送交到建立合併請求的時間。建立第一個合併請求後,資料將自動填入。"
+msgid ""
+"The coding stage shows the time from the first commit to creating the merge "
+"request. The data will automatically be added here once you create your "
+"first merge request."
+msgstr ""
+"程式開發階段顯示從第一次更動記錄 (commit) 到建立合併請求 (merge request) 的時間。建立第一個合併請求後,資料將自動填入。"
 
 msgid "The collection of events added to the data gathered for that stage."
-msgstr "與該階段相關的事件。"
+msgstr "該階段中的相關事件集合。"
+
+msgid "The fork relationship has been removed."
+msgstr "分支與主幹間的關聯 (fork relationship) 已被刪除。"
 
-msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage."
-msgstr "議題階段顯示從議題建立到設置里程碑、或將該議題加至議題看板的時間。建立第一個議題後,資料將自動填入。"
+msgid ""
+"The issue stage shows the time it takes from creating an issue to assigning "
+"the issue to a milestone, or add the issue to a list on your Issue Board. "
+"Begin creating issues to see data for this stage."
+msgstr ""
+"議題 (issue) 階段顯示從議題建立到設定里程碑所花的時間,或是議題被分類到議題看板 (issue board) "
+"中所花的時間。建立第一個議題後,資料將自動填入。"
 
 msgid "The phase of the development lifecycle."
-msgstr "專案開發生命週期的各個階段。"
+msgstr "專案開發週期的各個階段。"
 
-msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit."
-msgstr "計劃階段所顯示的是議題被排程後至第一個送交被推送的時間。一旦完成(或執行)首次的推送,資料將自動填入。"
+msgid ""
+"The pipelines schedule runs pipelines in the future, repeatedly, for "
+"specific branches or tags. Those scheduled pipelines will inherit limited "
+"project access based on their associated user."
+msgstr ""
+"在指定了特定分支 (branch) 或標籤後,此處的流水線 (pipeline) 排程會不斷地重複執行。\n"
+"流水線排程的存取權限與專案本身相同。"
 
-msgid "The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
-msgstr "上線階段顯示從建立一個議題到部署程式至線上的總時間。當完成從想法到產品實現的循環後,資料將自動填入。"
+msgid ""
+"The planning stage shows the time from the previous step to pushing your "
+"first commit. This time will be added automatically once you push your first "
+"commit."
+msgstr "計劃階段顯示從更動記錄 (commit) 被排程至第一個推送的時間。第一次推送之後,資料將自動填入。"
+
+msgid ""
+"The production stage shows the total time it takes between creating an issue "
+"and deploying the code to production. The data will be automatically added "
+"once you have completed the full idea to production cycle."
+msgstr "營運階段顯示從建立議題 (issue) 到部署程式上線所花的時間。完成從發想到上線的完整開發週期後,資料將自動填入。"
 
-msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request."
-msgstr "複閱階段顯示從合併請求建立後至被合併的時間。當建立第一個合併請求後,資料將自動填入。"
+msgid "The project can be accessed by any logged in user."
+msgstr "本專案可讓任何已登入的使用者存取"
+
+msgid "The project can be accessed without any authentication."
+msgstr "本專案可讓任何人存取"
+
+msgid "The repository for this project does not exist."
+msgstr "本專案沒有檔案庫 (repository) "
+
+msgid ""
+"The review stage shows the time from creating the merge request to merging "
+"it. The data will automatically be added after you merge your first merge "
+"request."
+msgstr ""
+"複閱階段顯示從合併請求 (merge request) 建立後至被合併的時間。當建立第一個合併請求 (merge request) 後,資料將自動填入。"
 
-msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time."
-msgstr "預備階段顯示從合併請求被合併後至部署上線的時間。當第一次部署上線後,資料將自動填入。"
+msgid ""
+"The staging stage shows the time between merging the MR and deploying code "
+"to the production environment. The data will be automatically added once you "
+"deploy to production for the first time."
+msgstr "試營運段顯示從合併請求 (merge request) 被合併後至部署營運的時間。當第一次部署營運後,資料將自動填入"
 
-msgid "The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running."
-msgstr "測試階段顯示相關合併請求的流水線所花的時間。當第一個流水線運作完畢後,資料將自動填入。"
+msgid ""
+"The testing stage shows the time GitLab CI takes to run every pipeline for "
+"the related merge request. The data will automatically be added after your "
+"first pipeline finishes running."
+msgstr ""
+"測試階段顯示相關合併請求 (merge request) 的流水線 (pipeline) 所花的時間。當第一個流水線 (pipeline) "
+"執行完畢後,資料將自動填入。"
 
 msgid "The time taken by each data entry gathered by that stage."
-msgstr "每筆該階段相關資料所花的時間。"
+msgstr "該階段中每一個資料項目所花的時間。"
 
-msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
+msgid ""
+"The value lying at the midpoint of a series of observed values. E.g., "
+"between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 ="
+" 6."
 msgstr "中位數是一個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。"
 
+msgid ""
+"This means you can not push code until you create an empty repository or "
+"import existing one."
+msgstr "這代表在您建立一個空的檔案庫 (repository) 或是匯入一個現存的檔案庫之前,您將無法上傳更新 (push) 。"
+
 msgid "Time before an issue gets scheduled"
-msgstr "議題等待排程的時間"
+msgstr "議題 (issue) 被列入日程表的時間"
 
 msgid "Time before an issue starts implementation"
-msgstr "議題等待開始實作的時間"
+msgstr "議題 (issue) 等待開始實作的時間"
 
 msgid "Time between merge request creation and merge/close"
-msgstr "合併請求被合併或是關閉的時間"
+msgstr "合併請求 (merge request) 從建立到被合併或是關閉的時間"
 
 msgid "Time until first merge request"
-msgstr "第一個合併請求被建立前的時間"
+msgstr "第一個合併請求 (merge request) 被建立前的時間"
+
+msgid "Timeago|%s days ago"
+msgstr " %s 天前"
+
+msgid "Timeago|%s days remaining"
+msgstr "剩下 %s 天"
+
+msgid "Timeago|%s hours remaining"
+msgstr "剩下 %s 小時"
+
+msgid "Timeago|%s minutes ago"
+msgstr " %s 分鐘前"
+
+msgid "Timeago|%s minutes remaining"
+msgstr "剩下 %s 分鐘"
+
+msgid "Timeago|%s months ago"
+msgstr " %s 個月前"
+
+msgid "Timeago|%s months remaining"
+msgstr "剩下 %s 月"
+
+msgid "Timeago|%s seconds remaining"
+msgstr "剩下 %s 秒"
+
+msgid "Timeago|%s weeks ago"
+msgstr " %s 週前"
+
+msgid "Timeago|%s weeks remaining"
+msgstr "剩下 %s 週"
+
+msgid "Timeago|%s years ago"
+msgstr " %s 年前"
+
+msgid "Timeago|%s years remaining"
+msgstr "剩下 %s 年"
+
+msgid "Timeago|1 day remaining"
+msgstr "剩下 1 天"
+
+msgid "Timeago|1 hour remaining"
+msgstr "剩下 1 小時"
+
+msgid "Timeago|1 minute remaining"
+msgstr "剩下 1 分鐘"
+
+msgid "Timeago|1 month remaining"
+msgstr "剩下 1 個月"
+
+msgid "Timeago|1 week remaining"
+msgstr "剩下 1 週"
+
+msgid "Timeago|1 year remaining"
+msgstr "剩下 1 年"
+
+msgid "Timeago|Past due"
+msgstr "逾期"
+
+msgid "Timeago|a day ago"
+msgstr " 1 天前"
+
+msgid "Timeago|a month ago"
+msgstr " 1 個月前"
+
+msgid "Timeago|a week ago"
+msgstr " 1 週前"
+
+msgid "Timeago|a while"
+msgstr "剛剛"
+
+msgid "Timeago|a year ago"
+msgstr " 1 年前"
+
+msgid "Timeago|about %s hours ago"
+msgstr "約 %s 小時前"
+
+msgid "Timeago|about a minute ago"
+msgstr "約 1 分鐘前"
+
+msgid "Timeago|about an hour ago"
+msgstr "約 1 小時前"
+
+msgid "Timeago|in %s days"
+msgstr " %s 天後"
+
+msgid "Timeago|in %s hours"
+msgstr " %s 小時後"
+
+msgid "Timeago|in %s minutes"
+msgstr " %s 分鐘後"
+
+msgid "Timeago|in %s months"
+msgstr " %s 個月後"
+
+msgid "Timeago|in %s seconds"
+msgstr " %s 秒後"
+
+msgid "Timeago|in %s weeks"
+msgstr " %s 週後"
+
+msgid "Timeago|in %s years"
+msgstr " %s 年後"
+
+msgid "Timeago|in 1 day"
+msgstr " 1 天後"
+
+msgid "Timeago|in 1 hour"
+msgstr " 1 小時後"
+
+msgid "Timeago|in 1 minute"
+msgstr " 1 分鐘後"
+
+msgid "Timeago|in 1 month"
+msgstr " 1 個月後"
+
+msgid "Timeago|in 1 week"
+msgstr " 1 週後"
+
+msgid "Timeago|in 1 year"
+msgstr " 1 年後"
+
+msgid "Timeago|less than a minute ago"
+msgstr "不到 1 分鐘前"
 
 msgid "Time|hr"
 msgid_plural "Time|hrs"
@@ -275,7 +974,28 @@ msgid "Total Time"
 msgstr "總時間"
 
 msgid "Total test time for all commits/merges"
-msgstr "所有送交和合併的總測試時間"
+msgstr "合併 (merge) 與更動記錄 (commit) 的總測試時間"
+
+msgid "Unstar"
+msgstr "取消收藏"
+
+msgid "Upload New File"
+msgstr "上傳新檔案"
+
+msgid "Upload file"
+msgstr "上傳檔案"
+
+msgid "Use your global notification setting"
+msgstr "使用全域通知設定"
+
+msgid "VisibilityLevel|Internal"
+msgstr "內部"
+
+msgid "VisibilityLevel|Private"
+msgstr "私有"
+
+msgid "VisibilityLevel|Public"
+msgstr "公開"
 
 msgid "Want to see the data? Please ask an administrator for access."
 msgstr "權限不足。如需查看相關資料,請向管理員申請權限。"
@@ -283,9 +1003,85 @@ msgstr "權限不足。如需查看相關資料,請向管理員申請權限。
 msgid "We don't have enough data to show this stage."
 msgstr "因該階段的資料不足而無法顯示相關資訊"
 
+msgid "Withdraw Access Request"
+msgstr "取消權限申請"
+
+msgid ""
+"You are going to remove %{project_name_with_namespace}.\n"
+"Removed project CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr ""
+"即將要刪除 %{project_name_with_namespace}。\n"
+"被刪除的專案完全無法救回來喔!\n"
+"真的「100%確定」要這麼做嗎?"
+
+msgid ""
+"You are going to remove the fork relationship to source project "
+"%{forked_from_project}. Are you ABSOLUTELY sure?"
+msgstr ""
+"將要刪除本分支專案與主幹的所有關聯 (fork relationship) 。 %{forked_from_project} "
+"真的「100%確定」要這麼做嗎?"
+
+msgid ""
+"You are going to transfer %{project_name_with_namespace} to another owner. "
+"Are you ABSOLUTELY sure?"
+msgstr "將要把 %{project_name_with_namespace} 的所有權轉移給另一個人。真的「100%確定」要這麼做嗎?"
+
+msgid "You can only add files when you are on a branch"
+msgstr "只能在分支 (branch) 上建立檔案"
+
+msgid "You have reached your project limit"
+msgstr "您已達到專案數量限制"
+
+msgid "You must sign in to star a project"
+msgstr "必須登入才能收藏專案"
+
 msgid "You need permission."
-msgstr "您需要相關的權限。"
+msgstr "需要權限才能這麼做。"
+
+msgid "You will not get any notifications via email"
+msgstr "不會收到任何通知郵件"
+
+msgid "You will only receive notifications for the events you choose"
+msgstr "只接收您選擇的事件通知"
+
+msgid ""
+"You will only receive notifications for threads you have participated in"
+msgstr "只接收參與主題的通知"
+
+msgid "You will receive notifications for any activity"
+msgstr "接收所有活動的通知"
+
+msgid ""
+"You will receive notifications only for comments in which you were "
+"@mentioned"
+msgstr "只接收評論中提及(@)您的通知"
+
+msgid ""
+"You won't be able to pull or push project code via %{protocol} until you "
+"%{set_password_link} on your account"
+msgstr ""
+"在帳號上 %{set_password_link} 之前, 將無法使用 %{protocol} 上傳 (push) 或下載 (pull) 程式碼。"
+
+msgid ""
+"You won't be able to pull or push project code via SSH until you "
+"%{add_ssh_key_link} to your profile"
+msgstr "在個人帳號中 %{add_ssh_key_link} 之前, 將無法使用 SSH 上傳 (push) 或下載 (pull) 程式碼。"
+
+msgid "Your name"
+msgstr "您的名字"
 
 msgid "day"
 msgid_plural "days"
 msgstr[0] "天"
+
+msgid "new merge request"
+msgstr "建立合併請求"
+
+msgid "notification emails"
+msgstr "通知信"
+
+msgid "parent"
+msgid_plural "parents"
+msgstr[0] "上層"
+
diff --git a/package.json b/package.json
index 045f07ee2f9970a0ee8aae97e2ea084aa854633c..5a997e813f8d032dad368e1116674ab64489f4b5 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
   },
   "dependencies": {
     "babel-core": "^6.22.1",
+    "babel-eslint": "^7.2.1",
     "babel-loader": "^6.2.10",
     "babel-plugin-transform-define": "^1.2.0",
     "babel-preset-latest": "^6.24.0",
diff --git a/qa/Dockerfile b/qa/Dockerfile
index 9e2a74ef991ec3844011d7188bedc2a5d02daddc..f3a81a7e355ac6f30ba524b939a28a2e5187de88 100644
--- a/qa/Dockerfile
+++ b/qa/Dockerfile
@@ -1,5 +1,6 @@
 FROM ruby:2.3
 LABEL maintainer "Grzegorz Bizon <grzegorz@gitlab.com>"
+ENV DEBIAN_FRONTEND noninteractive
 
 ##
 # Update APT sources and install some dependencies
@@ -8,25 +9,21 @@ RUN sed -i "s/httpredir.debian.org/ftp.us.debian.org/" /etc/apt/sources.list
 RUN apt-get update && apt-get install -y wget git unzip xvfb
 
 ##
-# At this point Google Chrome Beta is 59 - first version with headless support
+# Install Google Chrome version with headless support
 #
-RUN wget -q https://dl.google.com/linux/direct/google-chrome-beta_current_amd64.deb
-RUN dpkg -i google-chrome-beta_current_amd64.deb; apt-get -fy install
+RUN curl -sS -L https://dl.google.com/linux/linux_signing_key.pub | apt-key add -
+RUN echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google.list
+RUN apt-get update -q && apt-get install -y google-chrome-stable && apt-get clean
 
 ##
 # Install chromedriver to make it work with Selenium
 #
-RUN wget -q https://chromedriver.storage.googleapis.com/2.29/chromedriver_linux64.zip
+RUN wget -q https://chromedriver.storage.googleapis.com/$(wget -q -O - https://chromedriver.storage.googleapis.com/LATEST_RELEASE)/chromedriver_linux64.zip
 RUN unzip chromedriver_linux64.zip -d /usr/local/bin
 
-RUN apt-get clean
-
 WORKDIR /home/qa
-
 COPY ./Gemfile* ./
-
 RUN bundle install
-
 COPY ./ ./
 
 ENTRYPOINT ["bin/test"]
diff --git a/qa/qa/specs/config.rb b/qa/qa/specs/config.rb
index 78a93828d36b17c706befdc0f6ab90740eb6976d..b341aa3094afef77005cf3eb6ce900e39fb340a8 100644
--- a/qa/qa/specs/config.rb
+++ b/qa/qa/specs/config.rb
@@ -55,7 +55,7 @@ module QA
         Capybara.register_driver :chrome do |app|
           capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
             'chromeOptions' => {
-              'binary' => '/opt/google/chrome-beta/google-chrome-beta',
+              'binary' => '/usr/bin/google-chrome-stable',
               'args' => %w[headless no-sandbox disable-gpu]
             }
           )
diff --git a/rubocop/cop/project_path_helper.rb b/rubocop/cop/project_path_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3e1ce71ac0665ea0d20a19067d9318887e34df00
--- /dev/null
+++ b/rubocop/cop/project_path_helper.rb
@@ -0,0 +1,51 @@
+module RuboCop
+  module Cop
+    class ProjectPathHelper < RuboCop::Cop::Cop
+      MSG = 'Use short project path helpers without explicitly passing the namespace: ' \
+        '`foo_project_bar_path(project, bar)` instead of ' \
+        '`foo_namespace_project_bar_path(project.namespace, project, bar)`.'.freeze
+
+      METHOD_NAME_PATTERN = /\A([a-z_]+_)?namespace_project(?:_[a-z_]+)?_(?:url|path)\z/.freeze
+
+      def on_send(node)
+        return unless method_name(node).to_s =~ METHOD_NAME_PATTERN
+
+        namespace_expr, project_expr = arguments(node)
+        return unless namespace_expr && project_expr
+
+        return unless namespace_expr.type == :send
+        return unless method_name(namespace_expr) == :namespace
+        return unless receiver(namespace_expr) == project_expr
+
+        add_offense(node, :selector)
+      end
+
+      def autocorrect(node)
+        helper_name = method_name(node).to_s.sub('namespace_project', 'project')
+
+        arguments = arguments(node)
+        arguments.shift # Remove namespace argument
+
+        replacement = "#{helper_name}(#{arguments.map(&:source).join(', ')})"
+
+        lambda do |corrector|
+          corrector.replace(node.source_range, replacement)
+        end
+      end
+
+      private
+
+      def receiver(node)
+        node.children[0]
+      end
+
+      def method_name(node)
+        node.children[1]
+      end
+
+      def arguments(node)
+        node.children[2..-1]
+      end
+    end
+  end
+end
diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb
index 55d7708fa8cfb22b25b50b06dbe5403dbbdcf3f3..69b4b29507cc8c392f7dd155861f43205dd0c73c 100644
--- a/rubocop/rubocop.rb
+++ b/rubocop/rubocop.rb
@@ -3,6 +3,7 @@ require_relative 'cop/gem_fetcher'
 require_relative 'cop/activerecord_serialize'
 require_relative 'cop/redirect_with_status'
 require_relative 'cop/polymorphic_associations'
+require_relative 'cop/project_path_helper'
 require_relative 'cop/migration/add_column'
 require_relative 'cop/migration/add_column_with_default_to_large_table'
 require_relative 'cop/migration/add_concurrent_foreign_key'
diff --git a/spec/controllers/abuse_reports_controller_spec.rb b/spec/controllers/abuse_reports_controller_spec.rb
index 80a418feb3e469e3b4b1cd30528b590b7c87bc4e..ada011e7595af063a1c664fff8a15274d2a7c4f6 100644
--- a/spec/controllers/abuse_reports_controller_spec.rb
+++ b/spec/controllers/abuse_reports_controller_spec.rb
@@ -13,6 +13,31 @@ describe AbuseReportsController do
     sign_in(reporter)
   end
 
+  describe 'GET new' do
+    context 'when the user has already been deleted' do
+      it 'redirects the reporter to root_path' do
+        user_id = user.id
+        user.destroy
+
+        get :new, { user_id: user_id }
+
+        expect(response).to redirect_to root_path
+        expect(flash[:alert]).to eq('Cannot create the abuse report. The user has been deleted.')
+      end
+    end
+
+    context 'when the user has already been blocked' do
+      it 'redirects the reporter to the user\'s profile' do
+        user.block
+
+        get :new, { user_id: user.id }
+
+        expect(response).to redirect_to user
+        expect(flash[:alert]).to eq('Cannot create the abuse report. This user has been blocked.')
+      end
+    end
+  end
+
   describe 'POST create' do
     context 'with valid attributes' do
       it 'saves the abuse report' do
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index 7d6c317482f510eaa71dd5d6fd7efba2b2aab2c4..69928a906c6639844b25be93ed4ab2f50381997f 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -116,8 +116,8 @@ describe Admin::UsersController do
     it 'displays an alert' do
       go
 
-      expect(flash[:notice]).
-        to eq 'Two-factor Authentication has been disabled for this user'
+      expect(flash[:notice])
+        .to eq 'Two-factor Authentication has been disabled for this user'
     end
 
     def go
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index 3f99e2ff5965b62ab03cb13779f4dcb29ae4f90f..a2720c9b81e5a8ae64ba335b52be2fec2353faad 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -99,6 +99,36 @@ describe ApplicationController do
     end
   end
 
+  describe 'response format' do
+    controller(described_class) do
+      def index
+        respond_to do |format|
+          format.json do
+            head :ok
+          end
+        end
+      end
+    end
+
+    context 'when format is handled' do
+      let(:requested_format) { :json }
+
+      it 'returns 200 response' do
+        get :index, private_token: user.private_token, format: requested_format
+
+        expect(response).to have_http_status 200
+      end
+    end
+
+    context 'when format is not handled' do
+      it 'returns 404 response' do
+        get :index, private_token: user.private_token
+
+        expect(response).to have_http_status 404
+      end
+    end
+  end
+
   describe '#authenticate_user_from_rss_token' do
     describe "authenticating a user from an RSS token" do
       controller(described_class) do
diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb
index f3263bc177d4db75f851df34fe2e011ffda6ffaa..c6e5fb61cf9f8bfd4660414fd21f510c07196884 100644
--- a/spec/controllers/groups/milestones_controller_spec.rb
+++ b/spec/controllers/groups/milestones_controller_spec.rb
@@ -23,6 +23,21 @@ describe Groups::MilestonesController do
     project.team << [user, :master]
   end
 
+  describe "#index" do
+    it 'shows group milestones page' do
+      get :index, group_id: group.to_param
+
+      expect(response).to have_http_status(200)
+    end
+
+    it 'shows group milestones JSON' do
+      get :index, group_id: group.to_param, format: :json
+
+      expect(response).to have_http_status(200)
+      expect(response.content_type).to eq 'application/json'
+    end
+  end
+
   it_behaves_like 'milestone tabs'
 
   describe "#create" do
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index b0b24b1de1bcdd67ed1429d342c258c6639457c9..c4092303a672c71b911fa53a22a82c817aa1df70 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -2,7 +2,7 @@ require 'rails_helper'
 
 describe GroupsController do
   let(:user) { create(:user) }
-  let(:group) { create(:group) }
+  let(:group) { create(:group, :public) }
   let(:project) { create(:empty_project, namespace: group) }
   let!(:group_member) { create(:group_member, group: group, user: user) }
 
@@ -35,14 +35,15 @@ describe GroupsController do
         sign_in(user)
       end
 
-      it 'shows the public subgroups' do
+      it 'shows all subgroups' do
         get :subgroups, id: group.to_param
 
-        expect(assigns(:nested_groups)).to contain_exactly(public_subgroup)
+        expect(assigns(:nested_groups)).to contain_exactly(public_subgroup, private_subgroup)
       end
 
-      context 'being member' do
+      context 'being member of private subgroup' do
         it 'shows public and private subgroups the user is member of' do
+          group_member.destroy!
           private_subgroup.add_guest(user)
 
           get :subgroups, id: group.to_param
diff --git a/spec/controllers/import/bitbucket_controller_spec.rb b/spec/controllers/import/bitbucket_controller_spec.rb
index 0be7bc6a0452cd7fb9e9b8882577ceb38c7bf50c..8ef10dabd4c4d733ee90a62b2dc921c6ff9fa3ce 100644
--- a/spec/controllers/import/bitbucket_controller_spec.rb
+++ b/spec/controllers/import/bitbucket_controller_spec.rb
@@ -31,8 +31,8 @@ describe Import::BitbucketController do
                             expires_at: expires_at,
                             expires_in: expires_in,
                             refresh_token: refresh_token)
-      allow_any_instance_of(OAuth2::Client).
-        to receive(:get_token).and_return(access_token)
+      allow_any_instance_of(OAuth2::Client)
+        .to receive(:get_token).and_return(access_token)
       stub_omniauth_provider('bitbucket')
 
       get :callback
@@ -93,9 +93,9 @@ describe Import::BitbucketController do
     context "when the repository owner is the Bitbucket user" do
       context "when the Bitbucket user and GitLab user's usernames match" do
         it "takes the current user's namespace" do
-          expect(Gitlab::BitbucketImport::ProjectCreator).
-            to receive(:new).with(bitbucket_repo, bitbucket_repo.name, user.namespace, user, access_params).
-            and_return(double(execute: true))
+          expect(Gitlab::BitbucketImport::ProjectCreator)
+            .to receive(:new).with(bitbucket_repo, bitbucket_repo.name, user.namespace, user, access_params)
+            .and_return(double(execute: true))
 
           post :create, format: :js
         end
@@ -105,9 +105,9 @@ describe Import::BitbucketController do
         let(:bitbucket_username) { "someone_else" }
 
         it "takes the current user's namespace" do
-          expect(Gitlab::BitbucketImport::ProjectCreator).
-            to receive(:new).with(bitbucket_repo, bitbucket_repo.name, user.namespace, user, access_params).
-            and_return(double(execute: true))
+          expect(Gitlab::BitbucketImport::ProjectCreator)
+            .to receive(:new).with(bitbucket_repo, bitbucket_repo.name, user.namespace, user, access_params)
+            .and_return(double(execute: true))
 
           post :create, format: :js
         end
@@ -141,9 +141,9 @@ describe Import::BitbucketController do
           end
 
           it "takes the existing namespace" do
-            expect(Gitlab::BitbucketImport::ProjectCreator).
-              to receive(:new).with(bitbucket_repo, bitbucket_repo.name, existing_namespace, user, access_params).
-              and_return(double(execute: true))
+            expect(Gitlab::BitbucketImport::ProjectCreator)
+              .to receive(:new).with(bitbucket_repo, bitbucket_repo.name, existing_namespace, user, access_params)
+              .and_return(double(execute: true))
 
             post :create, format: :js
           end
@@ -151,8 +151,8 @@ describe Import::BitbucketController do
 
         context "when the namespace is not owned by the GitLab user" do
           it "doesn't create a project" do
-            expect(Gitlab::BitbucketImport::ProjectCreator).
-              not_to receive(:new)
+            expect(Gitlab::BitbucketImport::ProjectCreator)
+              .not_to receive(:new)
 
             post :create, format: :js
           end
@@ -162,16 +162,16 @@ describe Import::BitbucketController do
       context "when a namespace with the Bitbucket user's username doesn't exist" do
         context "when current user can create namespaces" do
           it "creates the namespace" do
-            expect(Gitlab::BitbucketImport::ProjectCreator).
-              to receive(:new).and_return(double(execute: true))
+            expect(Gitlab::BitbucketImport::ProjectCreator)
+              .to receive(:new).and_return(double(execute: true))
 
             expect { post :create, format: :js }.to change(Namespace, :count).by(1)
           end
 
           it "takes the new namespace" do
-            expect(Gitlab::BitbucketImport::ProjectCreator).
-              to receive(:new).with(bitbucket_repo, bitbucket_repo.name, an_instance_of(Group), user, access_params).
-              and_return(double(execute: true))
+            expect(Gitlab::BitbucketImport::ProjectCreator)
+              .to receive(:new).with(bitbucket_repo, bitbucket_repo.name, an_instance_of(Group), user, access_params)
+              .and_return(double(execute: true))
 
             post :create, format: :js
           end
@@ -183,16 +183,16 @@ describe Import::BitbucketController do
           end
 
           it "doesn't create the namespace" do
-            expect(Gitlab::BitbucketImport::ProjectCreator).
-              to receive(:new).and_return(double(execute: true))
+            expect(Gitlab::BitbucketImport::ProjectCreator)
+              .to receive(:new).and_return(double(execute: true))
 
             expect { post :create, format: :js }.not_to change(Namespace, :count)
           end
 
           it "takes the current user's namespace" do
-            expect(Gitlab::BitbucketImport::ProjectCreator).
-              to receive(:new).with(bitbucket_repo, bitbucket_repo.name, user.namespace, user, access_params).
-              and_return(double(execute: true))
+            expect(Gitlab::BitbucketImport::ProjectCreator)
+              .to receive(:new).with(bitbucket_repo, bitbucket_repo.name, user.namespace, user, access_params)
+              .and_return(double(execute: true))
 
             post :create, format: :js
           end
@@ -210,9 +210,9 @@ describe Import::BitbucketController do
       end
 
       it 'takes the selected namespace and name' do
-        expect(Gitlab::BitbucketImport::ProjectCreator).
-          to receive(:new).with(bitbucket_repo, test_name, nested_namespace, user, access_params).
-            and_return(double(execute: true))
+        expect(Gitlab::BitbucketImport::ProjectCreator)
+          .to receive(:new).with(bitbucket_repo, test_name, nested_namespace, user, access_params)
+            .and_return(double(execute: true))
 
         post :create, { target_namespace: nested_namespace.full_path, new_name: test_name, format: :js }
       end
@@ -222,26 +222,26 @@ describe Import::BitbucketController do
       let(:test_name) { 'test_name' }
 
       it 'takes the selected namespace and name' do
-        expect(Gitlab::BitbucketImport::ProjectCreator).
-          to receive(:new).with(bitbucket_repo, test_name, kind_of(Namespace), user, access_params).
-            and_return(double(execute: true))
+        expect(Gitlab::BitbucketImport::ProjectCreator)
+          .to receive(:new).with(bitbucket_repo, test_name, kind_of(Namespace), user, access_params)
+            .and_return(double(execute: true))
 
         post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :js }
       end
 
       it 'creates the namespaces' do
-        allow(Gitlab::BitbucketImport::ProjectCreator).
-          to receive(:new).with(bitbucket_repo, test_name, kind_of(Namespace), user, access_params).
-            and_return(double(execute: true))
+        allow(Gitlab::BitbucketImport::ProjectCreator)
+          .to receive(:new).with(bitbucket_repo, test_name, kind_of(Namespace), user, access_params)
+            .and_return(double(execute: true))
 
         expect { post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :js } }
           .to change { Namespace.count }.by(2)
       end
 
       it 'new namespace has the right parent' do
-        allow(Gitlab::BitbucketImport::ProjectCreator).
-          to receive(:new).with(bitbucket_repo, test_name, kind_of(Namespace), user, access_params).
-            and_return(double(execute: true))
+        allow(Gitlab::BitbucketImport::ProjectCreator)
+          .to receive(:new).with(bitbucket_repo, test_name, kind_of(Namespace), user, access_params)
+            .and_return(double(execute: true))
 
         post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :js }
 
@@ -254,17 +254,17 @@ describe Import::BitbucketController do
       let!(:parent_namespace) { create(:group, name: 'foo', owner: user) }
 
       it 'takes the selected namespace and name' do
-        expect(Gitlab::BitbucketImport::ProjectCreator).
-          to receive(:new).with(bitbucket_repo, test_name, kind_of(Namespace), user, access_params).
-            and_return(double(execute: true))
+        expect(Gitlab::BitbucketImport::ProjectCreator)
+          .to receive(:new).with(bitbucket_repo, test_name, kind_of(Namespace), user, access_params)
+            .and_return(double(execute: true))
 
         post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :js }
       end
 
       it 'creates the namespaces' do
-        allow(Gitlab::BitbucketImport::ProjectCreator).
-          to receive(:new).with(bitbucket_repo, test_name, kind_of(Namespace), user, access_params).
-            and_return(double(execute: true))
+        allow(Gitlab::BitbucketImport::ProjectCreator)
+          .to receive(:new).with(bitbucket_repo, test_name, kind_of(Namespace), user, access_params)
+            .and_return(double(execute: true))
 
         expect { post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :js } }
           .to change { Namespace.count }.by(2)
diff --git a/spec/controllers/import/github_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb
index 95696e14b6c5db12cb77e77fe84e05edcfa16c93..45c3fa075ef039688ad37c6ef07ce1a25121a595 100644
--- a/spec/controllers/import/github_controller_spec.rb
+++ b/spec/controllers/import/github_controller_spec.rb
@@ -21,10 +21,10 @@ describe Import::GithubController do
   describe "GET callback" do
     it "updates access token" do
       token = "asdasd12345"
-      allow_any_instance_of(Gitlab::GithubImport::Client).
-        to receive(:get_token).and_return(token)
-      allow_any_instance_of(Gitlab::GithubImport::Client).
-        to receive(:github_options).and_return({})
+      allow_any_instance_of(Gitlab::GithubImport::Client)
+        .to receive(:get_token).and_return(token)
+      allow_any_instance_of(Gitlab::GithubImport::Client)
+        .to receive(:github_options).and_return({})
       stub_omniauth_provider('github')
 
       get :callback
diff --git a/spec/controllers/import/gitlab_controller_spec.rb b/spec/controllers/import/gitlab_controller_spec.rb
index 3afd09063d7efc6674ec0c83252e45f3eecf2cda..997107dadeab8d88b4155202d24ccecd312ed3b1 100644
--- a/spec/controllers/import/gitlab_controller_spec.rb
+++ b/spec/controllers/import/gitlab_controller_spec.rb
@@ -18,8 +18,8 @@ describe Import::GitlabController do
 
   describe "GET callback" do
     it "updates access token" do
-      allow_any_instance_of(Gitlab::GitlabImport::Client).
-        to receive(:get_token).and_return(token)
+      allow_any_instance_of(Gitlab::GitlabImport::Client)
+        .to receive(:get_token).and_return(token)
       stub_omniauth_provider('gitlab')
 
       get :callback
@@ -78,9 +78,9 @@ describe Import::GitlabController do
     context "when the repository owner is the GitLab.com user" do
       context "when the GitLab.com user and GitLab server user's usernames match" do
         it "takes the current user's namespace" do
-          expect(Gitlab::GitlabImport::ProjectCreator).
-            to receive(:new).with(gitlab_repo, user.namespace, user, access_params).
-            and_return(double(execute: true))
+          expect(Gitlab::GitlabImport::ProjectCreator)
+            .to receive(:new).with(gitlab_repo, user.namespace, user, access_params)
+            .and_return(double(execute: true))
 
           post :create, format: :js
         end
@@ -90,9 +90,9 @@ describe Import::GitlabController do
         let(:gitlab_username) { "someone_else" }
 
         it "takes the current user's namespace" do
-          expect(Gitlab::GitlabImport::ProjectCreator).
-            to receive(:new).with(gitlab_repo, user.namespace, user, access_params).
-            and_return(double(execute: true))
+          expect(Gitlab::GitlabImport::ProjectCreator)
+            .to receive(:new).with(gitlab_repo, user.namespace, user, access_params)
+            .and_return(double(execute: true))
 
           post :create, format: :js
         end
@@ -116,9 +116,9 @@ describe Import::GitlabController do
           end
 
           it "takes the existing namespace" do
-            expect(Gitlab::GitlabImport::ProjectCreator).
-              to receive(:new).with(gitlab_repo, existing_namespace, user, access_params).
-              and_return(double(execute: true))
+            expect(Gitlab::GitlabImport::ProjectCreator)
+              .to receive(:new).with(gitlab_repo, existing_namespace, user, access_params)
+              .and_return(double(execute: true))
 
             post :create, format: :js
           end
@@ -126,8 +126,8 @@ describe Import::GitlabController do
 
         context "when the namespace is not owned by the GitLab server user" do
           it "doesn't create a project" do
-            expect(Gitlab::GitlabImport::ProjectCreator).
-              not_to receive(:new)
+            expect(Gitlab::GitlabImport::ProjectCreator)
+              .not_to receive(:new)
 
             post :create, format: :js
           end
@@ -137,16 +137,16 @@ describe Import::GitlabController do
       context "when a namespace with the GitLab.com user's username doesn't exist" do
         context "when current user can create namespaces" do
           it "creates the namespace" do
-            expect(Gitlab::GitlabImport::ProjectCreator).
-              to receive(:new).and_return(double(execute: true))
+            expect(Gitlab::GitlabImport::ProjectCreator)
+              .to receive(:new).and_return(double(execute: true))
 
             expect { post :create, format: :js }.to change(Namespace, :count).by(1)
           end
 
           it "takes the new namespace" do
-            expect(Gitlab::GitlabImport::ProjectCreator).
-              to receive(:new).with(gitlab_repo, an_instance_of(Group), user, access_params).
-              and_return(double(execute: true))
+            expect(Gitlab::GitlabImport::ProjectCreator)
+              .to receive(:new).with(gitlab_repo, an_instance_of(Group), user, access_params)
+              .and_return(double(execute: true))
 
             post :create, format: :js
           end
@@ -158,16 +158,16 @@ describe Import::GitlabController do
           end
 
           it "doesn't create the namespace" do
-            expect(Gitlab::GitlabImport::ProjectCreator).
-              to receive(:new).and_return(double(execute: true))
+            expect(Gitlab::GitlabImport::ProjectCreator)
+              .to receive(:new).and_return(double(execute: true))
 
             expect { post :create, format: :js }.not_to change(Namespace, :count)
           end
 
           it "takes the current user's namespace" do
-            expect(Gitlab::GitlabImport::ProjectCreator).
-              to receive(:new).with(gitlab_repo, user.namespace, user, access_params).
-              and_return(double(execute: true))
+            expect(Gitlab::GitlabImport::ProjectCreator)
+              .to receive(:new).with(gitlab_repo, user.namespace, user, access_params)
+              .and_return(double(execute: true))
 
             post :create, format: :js
           end
@@ -183,9 +183,9 @@ describe Import::GitlabController do
         end
 
         it 'takes the selected namespace and name' do
-          expect(Gitlab::GitlabImport::ProjectCreator).
-            to receive(:new).with(gitlab_repo, nested_namespace, user, access_params).
-              and_return(double(execute: true))
+          expect(Gitlab::GitlabImport::ProjectCreator)
+            .to receive(:new).with(gitlab_repo, nested_namespace, user, access_params)
+              .and_return(double(execute: true))
 
           post :create, { target_namespace: nested_namespace.full_path, format: :js }
         end
@@ -195,26 +195,26 @@ describe Import::GitlabController do
         let(:test_name) { 'test_name' }
 
         it 'takes the selected namespace and name' do
-          expect(Gitlab::GitlabImport::ProjectCreator).
-            to receive(:new).with(gitlab_repo, kind_of(Namespace), user, access_params).
-              and_return(double(execute: true))
+          expect(Gitlab::GitlabImport::ProjectCreator)
+            .to receive(:new).with(gitlab_repo, kind_of(Namespace), user, access_params)
+              .and_return(double(execute: true))
 
           post :create, { target_namespace: 'foo/bar', format: :js }
         end
 
         it 'creates the namespaces' do
-          allow(Gitlab::GitlabImport::ProjectCreator).
-            to receive(:new).with(gitlab_repo, kind_of(Namespace), user, access_params).
-              and_return(double(execute: true))
+          allow(Gitlab::GitlabImport::ProjectCreator)
+            .to receive(:new).with(gitlab_repo, kind_of(Namespace), user, access_params)
+              .and_return(double(execute: true))
 
           expect { post :create, { target_namespace: 'foo/bar', format: :js } }
             .to change { Namespace.count }.by(2)
         end
 
         it 'new namespace has the right parent' do
-          allow(Gitlab::GitlabImport::ProjectCreator).
-            to receive(:new).with(gitlab_repo, kind_of(Namespace), user, access_params).
-              and_return(double(execute: true))
+          allow(Gitlab::GitlabImport::ProjectCreator)
+            .to receive(:new).with(gitlab_repo, kind_of(Namespace), user, access_params)
+              .and_return(double(execute: true))
 
           post :create, { target_namespace: 'foo/bar', format: :js }
 
@@ -227,17 +227,17 @@ describe Import::GitlabController do
         let!(:parent_namespace) { create(:group, name: 'foo', owner: user) }
 
         it 'takes the selected namespace and name' do
-          expect(Gitlab::GitlabImport::ProjectCreator).
-            to receive(:new).with(gitlab_repo, kind_of(Namespace), user, access_params).
-              and_return(double(execute: true))
+          expect(Gitlab::GitlabImport::ProjectCreator)
+            .to receive(:new).with(gitlab_repo, kind_of(Namespace), user, access_params)
+              .and_return(double(execute: true))
 
           post :create, { target_namespace: 'foo/foobar/bar', format: :js }
         end
 
         it 'creates the namespaces' do
-          allow(Gitlab::GitlabImport::ProjectCreator).
-            to receive(:new).with(gitlab_repo, kind_of(Namespace), user, access_params).
-              and_return(double(execute: true))
+          allow(Gitlab::GitlabImport::ProjectCreator)
+            .to receive(:new).with(gitlab_repo, kind_of(Namespace), user, access_params)
+              .and_return(double(execute: true))
 
           expect { post :create, { target_namespace: 'foo/foobar/bar', format: :js } }
             .to change { Namespace.count }.by(2)
diff --git a/spec/controllers/profiles/preferences_controller_spec.rb b/spec/controllers/profiles/preferences_controller_spec.rb
index 7b3aa0491c72b35712a902a67fc94fc7ca2ec60b..a5f544b4f9225eab6ae6193eee4e095550f606bb 100644
--- a/spec/controllers/profiles/preferences_controller_spec.rb
+++ b/spec/controllers/profiles/preferences_controller_spec.rb
@@ -43,7 +43,8 @@ describe Profiles::PreferencesController do
           dashboard: 'stars'
         }.with_indifferent_access
 
-        expect(user).to receive(:update_attributes).with(prefs)
+        expect(user).to receive(:assign_attributes).with(prefs)
+        expect(user).to receive(:save)
 
         go params: prefs
       end
@@ -51,7 +52,7 @@ describe Profiles::PreferencesController do
 
     context 'on failed update' do
       it 'sets the flash' do
-        expect(user).to receive(:update_attributes).and_return(false)
+        expect(user).to receive(:save).and_return(false)
 
         go
 
diff --git a/spec/controllers/projects/artifacts_controller_spec.rb b/spec/controllers/projects/artifacts_controller_spec.rb
index 428bc45b8421ec2627e5e455cd8cce1460d5d4bc..d2c613a2423948450ae1768635e27942072bb66a 100644
--- a/spec/controllers/projects/artifacts_controller_spec.rb
+++ b/spec/controllers/projects/artifacts_controller_spec.rb
@@ -134,10 +134,7 @@ describe Projects::ArtifactsController do
     context 'found the job and redirect' do
       shared_examples 'redirect to the job' do
         it 'redirects' do
-          path = browse_namespace_project_job_artifacts_path(
-            project.namespace,
-            project,
-            job)
+          path = browse_project_job_artifacts_path(project, job)
 
           expect(response).to redirect_to(path)
         end
@@ -174,11 +171,7 @@ describe Projects::ArtifactsController do
         end
 
         it 'redirects' do
-          path = file_namespace_project_job_artifacts_path(
-            project.namespace,
-            project,
-            job,
-            'README.md')
+          path = file_project_job_artifacts_path(project, job, 'README.md')
 
           expect(response).to redirect_to(path)
         end
diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb
index 3b3caa9d3e63b4d4fdae61587dd3ae8b8fbe7487..02bbc48dc593da4ab91030ccddac40b64f9f83ca 100644
--- a/spec/controllers/projects/blob_controller_spec.rb
+++ b/spec/controllers/projects/blob_controller_spec.rb
@@ -47,8 +47,8 @@ describe Projects::BlobController do
       context 'redirect to tree' do
         let(:id) { 'markdown/doc' }
         it 'redirects' do
-          expect(subject).
-            to redirect_to("/#{project.path_with_namespace}/tree/markdown/doc")
+          expect(subject)
+            .to redirect_to("/#{project.path_with_namespace}/tree/markdown/doc")
         end
       end
     end
@@ -117,7 +117,7 @@ describe Projects::BlobController do
       end
 
       it 'redirects to blob show' do
-        expect(response).to redirect_to(namespace_project_blob_path(project.namespace, project, 'master/CHANGELOG'))
+        expect(response).to redirect_to(project_blob_path(project, 'master/CHANGELOG'))
       end
     end
 
@@ -164,7 +164,7 @@ describe Projects::BlobController do
     end
 
     def blob_after_edit_path
-      namespace_project_blob_path(project.namespace, project, 'master/CHANGELOG')
+      project_blob_path(project, 'master/CHANGELOG')
     end
 
     before do
@@ -186,7 +186,7 @@ describe Projects::BlobController do
       it 'redirects to MR diff' do
         put :update, mr_params
 
-        after_edit_path = diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
+        after_edit_path = diffs_project_merge_request_path(project, merge_request)
         file_anchor = "##{Digest::SHA1.hexdigest('CHANGELOG')}"
         expect(response).to redirect_to(after_edit_path + file_anchor)
       end
@@ -223,7 +223,7 @@ describe Projects::BlobController do
         it 'redirects to blob' do
           put :update, default_params
 
-          expect(response).to redirect_to(namespace_project_blob_path(forked_project.namespace, forked_project, 'master/CHANGELOG'))
+          expect(response).to redirect_to(project_blob_path(forked_project, 'master/CHANGELOG'))
         end
       end
 
@@ -235,8 +235,7 @@ describe Projects::BlobController do
           put :update, default_params
 
           expect(response).to redirect_to(
-            new_namespace_project_merge_request_path(
-              forked_project.namespace,
+            project_new_merge_request_path(
               forked_project,
               merge_request: {
                 source_project_id: forked_project.id,
diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb
index f9e21f9d8f625d3947b855386cd47b2a9d3d0068..9cd4e9dbf84b8e99203ee6bfc25cf34861c547a3 100644
--- a/spec/controllers/projects/branches_controller_spec.rb
+++ b/spec/controllers/projects/branches_controller_spec.rb
@@ -32,8 +32,8 @@ describe Projects::BranchesController do
         let(:branch) { "merge_branch" }
         let(:ref) { "master" }
         it 'redirects' do
-          expect(subject).
-            to redirect_to("/#{project.path_with_namespace}/tree/merge_branch")
+          expect(subject)
+            .to redirect_to("/#{project.path_with_namespace}/tree/merge_branch")
         end
       end
 
@@ -41,8 +41,8 @@ describe Projects::BranchesController do
         let(:branch) { "<script>alert('merge');</script>" }
         let(:ref) { "master" }
         it 'redirects' do
-          expect(subject).
-            to redirect_to("/#{project.path_with_namespace}/tree/alert('merge');")
+          expect(subject)
+            .to redirect_to("/#{project.path_with_namespace}/tree/alert('merge');")
         end
       end
 
@@ -81,8 +81,8 @@ describe Projects::BranchesController do
           branch_name: branch,
           issue_iid: issue.iid
 
-        expect(subject).
-          to redirect_to("/#{project.path_with_namespace}/tree/1-feature-branch")
+        expect(subject)
+          .to redirect_to("/#{project.path_with_namespace}/tree/1-feature-branch")
       end
 
       it 'posts a system note' do
@@ -110,7 +110,7 @@ describe Projects::BranchesController do
             branch_name: branch,
             issue_iid: issue.iid
 
-          expect(response).to redirect_to namespace_project_tree_path(project.namespace, project, branch)
+          expect(response).to redirect_to project_tree_path(project, branch)
         end
 
         it 'redirects to autodeploy setup page' do
@@ -127,7 +127,7 @@ describe Projects::BranchesController do
             branch_name: branch,
             issue_iid: issue.iid
 
-          expect(response.location).to include(namespace_project_new_blob_path(project.namespace, project, branch))
+          expect(response.location).to include(project_new_blob_path(project, branch))
           expect(response).to have_http_status(302)
         end
       end
@@ -303,7 +303,7 @@ describe Projects::BranchesController do
 
       it 'redirects to branches path' do
         expect(response)
-          .to redirect_to(namespace_project_branches_path(project.namespace, project))
+          .to redirect_to(project_branches_path(project))
       end
     end
   end
@@ -323,7 +323,7 @@ describe Projects::BranchesController do
       it 'redirects to branches' do
         destroy_all_merged
 
-        expect(response).to redirect_to namespace_project_branches_path(project.namespace, project)
+        expect(response).to redirect_to project_branches_path(project)
       end
 
       it 'starts worker to delete merged branches' do
diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb
index 7fb08df1950f7871a94bff61130ffccf182f0e0a..eb61a0c080c6da396e8bc478fedde0d16a9b3146 100644
--- a/spec/controllers/projects/commit_controller_spec.rb
+++ b/spec/controllers/projects/commit_controller_spec.rb
@@ -66,8 +66,8 @@ describe Projects::CommitController do
       end
 
       it "does not escape Html" do
-        allow_any_instance_of(Commit).to receive(:"to_#{format}").
-          and_return('HTML entities &<>" ')
+        allow_any_instance_of(Commit).to receive(:"to_#{format}")
+          .and_return('HTML entities &<>" ')
 
         go(id: commit.id, format: format)
 
@@ -169,7 +169,7 @@ describe Projects::CommitController do
             start_branch: 'master',
             id: commit.id)
 
-        expect(response).to redirect_to namespace_project_commits_path(project.namespace, project, 'master')
+        expect(response).to redirect_to project_commits_path(project, 'master')
         expect(flash[:notice]).to eq('The commit has been successfully reverted.')
       end
     end
@@ -191,7 +191,7 @@ describe Projects::CommitController do
             start_branch: 'master',
             id: commit.id)
 
-        expect(response).to redirect_to namespace_project_commit_path(project.namespace, project, commit.id)
+        expect(response).to redirect_to project_commit_path(project, commit.id)
         expect(flash[:alert]).to match('Sorry, we cannot revert this commit automatically.')
       end
     end
@@ -218,7 +218,7 @@ describe Projects::CommitController do
             start_branch: 'master',
             id: master_pickable_commit.id)
 
-        expect(response).to redirect_to namespace_project_commits_path(project.namespace, project, 'master')
+        expect(response).to redirect_to project_commits_path(project, 'master')
         expect(flash[:notice]).to eq('The commit has been successfully cherry-picked.')
       end
     end
@@ -240,7 +240,7 @@ describe Projects::CommitController do
             start_branch: 'master',
             id: master_pickable_commit.id)
 
-        expect(response).to redirect_to namespace_project_commit_path(project.namespace, project, master_pickable_commit.id)
+        expect(response).to redirect_to project_commit_path(project, master_pickable_commit.id)
         expect(flash[:alert]).to match('Sorry, we cannot cherry-pick this commit automatically.')
       end
     end
diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb
index 8f4694c98542e84fa07e5f8ce627014f1a3c600c..b4f9fd9b7a21ad6503a8523c67db765a9a3106bf 100644
--- a/spec/controllers/projects/compare_controller_spec.rb
+++ b/spec/controllers/projects/compare_controller_spec.rb
@@ -72,7 +72,7 @@ describe Projects::CompareController do
            from: '',
            to: 'master')
 
-      expect(response).to redirect_to(namespace_project_compare_index_path(project.namespace, project, to: 'master'))
+      expect(response).to redirect_to(project_compare_index_path(project, to: 'master'))
     end
 
     it 'redirects back to index when params[:to] is empty and preserves params[:from]' do
@@ -82,7 +82,7 @@ describe Projects::CompareController do
            from: 'master',
            to: '')
 
-      expect(response).to redirect_to(namespace_project_compare_index_path(project.namespace, project, from: 'master'))
+      expect(response).to redirect_to(project_compare_index_path(project, from: 'master'))
     end
 
     it 'redirects back to index when params[:from] and params[:to] are empty' do
diff --git a/spec/controllers/projects/deployments_controller_spec.rb b/spec/controllers/projects/deployments_controller_spec.rb
index 4c69443314d75a02e880f55eefc2c15517983309..0dbfcf97f6feea969cc2a16fe8110e9cd9189a6e 100644
--- a/spec/controllers/projects/deployments_controller_spec.rb
+++ b/spec/controllers/projects/deployments_controller_spec.rb
@@ -42,6 +42,7 @@ describe Projects::DeploymentsController do
     before do
       allow(controller).to receive(:deployment).and_return(deployment)
     end
+
     context 'when metrics are disabled' do
       before do
         allow(deployment).to receive(:has_metrics?).and_return false
@@ -108,6 +109,69 @@ describe Projects::DeploymentsController do
     end
   end
 
+  describe 'GET #additional_metrics' do
+    let(:deployment) { create(:deployment, project: project, environment: environment) }
+
+    before do
+      allow(controller).to receive(:deployment).and_return(deployment)
+    end
+
+    context 'when metrics are disabled' do
+      before do
+        allow(deployment).to receive(:has_metrics?).and_return false
+      end
+
+      it 'responds with not found' do
+        get :metrics, deployment_params(id: deployment.id)
+
+        expect(response).to be_not_found
+      end
+    end
+
+    context 'when metrics are enabled' do
+      let(:prometheus_service) { double('prometheus_service') }
+
+      before do
+        allow(deployment.project).to receive(:prometheus_service).and_return(prometheus_service)
+      end
+
+      context 'when environment has no metrics' do
+        before do
+          expect(deployment).to receive(:additional_metrics).and_return({})
+        end
+
+        it 'returns a empty response 204 response' do
+          get :additional_metrics, deployment_params(id: deployment.id, format: :json)
+          expect(response).to have_http_status(204)
+          expect(response.body).to eq('')
+        end
+      end
+
+      context 'when environment has some metrics' do
+        let(:empty_metrics) do
+          {
+            success: true,
+            metrics: {},
+            last_update: 42
+          }
+        end
+
+        before do
+          expect(deployment).to receive(:additional_metrics).and_return(empty_metrics)
+        end
+
+        it 'returns a metrics JSON document' do
+          get :additional_metrics, deployment_params(id: deployment.id, format: :json)
+
+          expect(response).to be_ok
+          expect(json_response['success']).to be(true)
+          expect(json_response['metrics']).to eq({})
+          expect(json_response['last_update']).to eq(42)
+        end
+      end
+    end
+  end
+
   def deployment_params(opts = {})
     opts.reverse_merge(namespace_id: project.namespace,
                        project_id: project,
diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb
index f6840578145e285cf5cbd672fdca1d09fef787e1..9db8ff5bbaada52b481aae3b9fb1a9d6e6d77abd 100644
--- a/spec/controllers/projects/environments_controller_spec.rb
+++ b/spec/controllers/projects/environments_controller_spec.rb
@@ -58,9 +58,11 @@ describe Projects::EnvironmentsController do
           expect(json_response['stopped_count']).to eq 1
         end
 
-        it 'sets the polling interval header' do
+        it 'does not set the polling interval header' do
+          # TODO, this is a temporary fix, see follow up issue:
+          # https://gitlab.com/gitlab-org/gitlab-ee/issues/2677
           expect(response).to have_http_status(:ok)
-          expect(response.headers['Poll-Interval']).to eq("3000")
+          expect(response.headers['Poll-Interval']).to be_nil
         end
       end
 
@@ -182,7 +184,7 @@ describe Projects::EnvironmentsController do
         expect(response).to have_http_status(200)
         expect(json_response).to eq(
           { 'redirect_url' =>
-              namespace_project_job_url(project.namespace, project, action) })
+              project_job_url(project, action) })
       end
     end
 
@@ -196,7 +198,7 @@ describe Projects::EnvironmentsController do
         expect(response).to have_http_status(200)
         expect(json_response).to eq(
           { 'redirect_url' =>
-              namespace_project_environment_url(project.namespace, project, environment) })
+              project_environment_url(project, environment) })
       end
     end
   end
@@ -233,14 +235,14 @@ describe Projects::EnvironmentsController do
 
       context 'and valid id' do
         it 'returns the first terminal for the environment' do
-          expect_any_instance_of(Environment).
-            to receive(:terminals).
-            and_return([:fake_terminal])
+          expect_any_instance_of(Environment)
+            .to receive(:terminals)
+            .and_return([:fake_terminal])
 
-          expect(Gitlab::Workhorse).
-            to receive(:terminal_websocket).
-            with(:fake_terminal).
-            and_return(workhorse: :response)
+          expect(Gitlab::Workhorse)
+            .to receive(:terminal_websocket)
+            .with(:fake_terminal)
+            .and_return(workhorse: :response)
 
           get :terminal_websocket_authorize, environment_params
 
@@ -316,6 +318,48 @@ describe Projects::EnvironmentsController do
     end
   end
 
+  describe 'GET #additional_metrics' do
+    before do
+      allow(controller).to receive(:environment).and_return(environment)
+    end
+
+    context 'when environment has no metrics' do
+      before do
+        expect(environment).to receive(:additional_metrics).and_return(nil)
+      end
+
+      context 'when requesting metrics as JSON' do
+        it 'returns a metrics JSON document' do
+          get :additional_metrics, environment_params(format: :json)
+
+          expect(response).to have_http_status(204)
+          expect(json_response).to eq({})
+        end
+      end
+    end
+
+    context 'when environment has some metrics' do
+      before do
+        expect(environment)
+          .to receive(:additional_metrics)
+                .and_return({
+                              success: true,
+                              data: {},
+                              last_update: 42
+                            })
+      end
+
+      it 'returns a metrics JSON document' do
+        get :additional_metrics, environment_params(format: :json)
+
+        expect(response).to be_ok
+        expect(json_response['success']).to be(true)
+        expect(json_response['data']).to eq({})
+        expect(json_response['last_update']).to eq(42)
+      end
+    end
+  end
+
   def environment_params(opts = {})
     opts.reverse_merge(namespace_id: project.namespace,
                        project_id: project,
diff --git a/spec/controllers/projects/group_links_controller_spec.rb b/spec/controllers/projects/group_links_controller_spec.rb
index b5435357f532d8c085b76bfca4ddbf7d5c9075e1..48a2994cbc01d3b5dfeb8cf9bf7babf1518a859f 100644
--- a/spec/controllers/projects/group_links_controller_spec.rb
+++ b/spec/controllers/projects/group_links_controller_spec.rb
@@ -34,7 +34,7 @@ describe Projects::GroupLinksController do
 
       it 'redirects to project group links page' do
         expect(response).to redirect_to(
-          namespace_project_settings_members_path(project.namespace, project)
+          project_settings_members_path(project)
         )
       end
     end
@@ -65,7 +65,7 @@ describe Projects::GroupLinksController do
 
       it 'redirects to project group links page' do
         expect(response).to redirect_to(
-          namespace_project_settings_members_path(project.namespace, project)
+          project_settings_members_path(project)
         )
       end
     end
@@ -79,7 +79,7 @@ describe Projects::GroupLinksController do
 
       it 'redirects to project group links page' do
         expect(response).to redirect_to(
-          namespace_project_settings_members_path(project.namespace, project)
+          project_settings_members_path(project)
         )
         expect(flash[:alert]).to eq('Please select a group.')
       end
diff --git a/spec/controllers/projects/imports_controller_spec.rb b/spec/controllers/projects/imports_controller_spec.rb
index 6724b474179871b360c940617e5620e9de74b71f..9be613426161d1587e1d7ca2315609c74bd31b1d 100644
--- a/spec/controllers/projects/imports_controller_spec.rb
+++ b/spec/controllers/projects/imports_controller_spec.rb
@@ -59,7 +59,7 @@ describe Projects::ImportsController do
         it 'redirects to new_namespace_project_import_path' do
           get :show, namespace_id: project.namespace.to_param, project_id: project
 
-          expect(response).to redirect_to new_namespace_project_import_path(project.namespace, project)
+          expect(response).to redirect_to new_project_import_path(project)
         end
       end
 
@@ -75,7 +75,7 @@ describe Projects::ImportsController do
             get :show, namespace_id: project.namespace.to_param, project_id: project
 
             expect(flash[:notice]).to eq 'The project was successfully forked.'
-            expect(response).to redirect_to namespace_project_path(project.namespace, project)
+            expect(response).to redirect_to project_path(project)
           end
         end
 
@@ -84,14 +84,14 @@ describe Projects::ImportsController do
             get :show, namespace_id: project.namespace.to_param, project_id: project
 
             expect(flash[:notice]).to eq 'The project was successfully imported.'
-            expect(response).to redirect_to namespace_project_path(project.namespace, project)
+            expect(response).to redirect_to project_path(project)
           end
         end
 
         context 'when continue params is present' do
           let(:params) do
             {
-              to: namespace_project_path(project.namespace, project),
+              to: project_path(project),
               notice: 'Finished'
             }
           end
@@ -120,7 +120,7 @@ describe Projects::ImportsController do
         it 'redirects to namespace_project_path' do
           get :show, namespace_id: project.namespace.to_param, project_id: project
 
-          expect(response).to redirect_to namespace_project_path(project.namespace, project)
+          expect(response).to redirect_to project_path(project)
         end
       end
     end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index f853bfe370c3b130dbb9a92e00b12ab325c77952..22aad0b32257fa8689215600f6282a7d05a16aab 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -35,7 +35,7 @@ describe Projects::IssuesController do
       it "returns 301 if request path doesn't match project path" do
         get :index, namespace_id: project.namespace, project_id: project.path.upcase
 
-        expect(response).to redirect_to(namespace_project_issues_path(project.namespace, project))
+        expect(response).to redirect_to(project_issues_path(project))
       end
 
       it "returns 404 when issues are disabled" do
@@ -328,8 +328,8 @@ describe Projects::IssuesController do
             it 'redirect to issue page' do
               update_verified_issue
 
-              expect(response).
-                to redirect_to(namespace_project_issue_path(project.namespace, project, issue))
+              expect(response)
+                .to redirect_to(project_issue_path(project, issue))
             end
 
             it 'accepts an issue after recaptcha is verified' do
@@ -343,8 +343,8 @@ describe Projects::IssuesController do
             it 'does not mark spam log as recaptcha_verified when it does not belong to current_user' do
               spam_log = create(:spam_log)
 
-              expect { update_issue(spam_log_id: spam_log.id, recaptcha_verification: true) }.
-                not_to change { SpamLog.last.recaptcha_verified }
+              expect { update_issue(spam_log_id: spam_log.id, recaptcha_verification: true) }
+                .not_to change { SpamLog.last.recaptcha_verified }
             end
           end
         end
@@ -685,8 +685,8 @@ describe Projects::IssuesController do
           it 'does not mark spam log as recaptcha_verified when it does not belong to current_user' do
             spam_log = create(:spam_log)
 
-            expect { post_new_issue({}, { spam_log_id: spam_log.id, recaptcha_verification: true } ) }.
-              not_to change { SpamLog.last.recaptcha_verified }
+            expect { post_new_issue({}, { spam_log_id: spam_log.id, recaptcha_verification: true } ) }
+              .not_to change { SpamLog.last.recaptcha_verified }
           end
         end
       end
@@ -702,7 +702,7 @@ describe Projects::IssuesController do
       end
     end
 
-    context 'when description has slash commands' do
+    context 'when description has quick actions' do
       before do
         sign_in(user)
       end
diff --git a/spec/controllers/projects/labels_controller_spec.rb b/spec/controllers/projects/labels_controller_spec.rb
index bf1776eb3207bcf18fd70295e45510c9e799700d..f19ad4c2c8168edbf455063d8464c532b9095305 100644
--- a/spec/controllers/projects/labels_controller_spec.rb
+++ b/spec/controllers/projects/labels_controller_spec.rb
@@ -178,7 +178,7 @@ describe Projects::LabelsController do
             it 'redirects to the correct casing' do
               get :index, namespace_id: project.namespace, project_id: project.to_param.upcase
 
-              expect(response).to redirect_to(namespace_project_labels_path(project.namespace, project))
+              expect(response).to redirect_to(project_labels_path(project))
               expect(controller).not_to set_flash[:notice]
             end
           end
@@ -191,7 +191,7 @@ describe Projects::LabelsController do
         it 'redirects to the canonical path' do
           get :index, namespace_id: project.namespace, project_id: project.to_param + 'old'
 
-          expect(response).to redirect_to(namespace_project_labels_path(project.namespace, project))
+          expect(response).to redirect_to(project_labels_path(project))
           expect(controller).to set_flash[:notice].to(project_moved_message(redirect_route, project))
         end
       end
diff --git a/spec/controllers/projects/mattermosts_controller_spec.rb b/spec/controllers/projects/mattermosts_controller_spec.rb
index c5abf11cfa5df2472db7cc57990a7e514e7ca661..12e413db9025bd5eee7110ef634bd09f6d3cd92f 100644
--- a/spec/controllers/projects/mattermosts_controller_spec.rb
+++ b/spec/controllers/projects/mattermosts_controller_spec.rb
@@ -11,8 +11,8 @@ describe Projects::MattermostsController do
 
   describe 'GET #new' do
     before do
-      allow_any_instance_of(MattermostSlashCommandsService).
-        to receive(:list_teams).and_return([])
+      allow_any_instance_of(MattermostSlashCommandsService)
+        .to receive(:list_teams).and_return([])
     end
 
     it 'accepts the request' do
@@ -38,7 +38,7 @@ describe Projects::MattermostsController do
       it 'shows the error' do
         allow_any_instance_of(MattermostSlashCommandsService).to receive(:configure).and_return([false, "error message"])
 
-        expect(subject).to redirect_to(new_namespace_project_mattermost_url(project.namespace, project))
+        expect(subject).to redirect_to(new_project_mattermost_url(project))
       end
     end
 
@@ -51,7 +51,7 @@ describe Projects::MattermostsController do
         subject
         service = project.services.last
 
-        expect(subject).to redirect_to(edit_namespace_project_service_url(project.namespace, project, service))
+        expect(subject).to redirect_to(edit_project_service_url(project, service))
       end
     end
   end
diff --git a/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb b/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9278ac8edd88309f328a5dcc9ee3c6f25a5dec94
--- /dev/null
+++ b/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb
@@ -0,0 +1,307 @@
+require 'spec_helper'
+
+describe Projects::MergeRequests::ConflictsController do
+  let(:project) { create(:project) }
+  let(:user)    { project.owner }
+  let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
+  let(:merge_request_with_conflicts) do
+    create(:merge_request, source_branch: 'conflict-resolvable', target_branch: 'conflict-start', source_project: project) do |mr|
+      mr.mark_as_unmergeable
+    end
+  end
+
+  before do
+    sign_in(user)
+  end
+
+  describe 'GET show' do
+    context 'when the conflicts cannot be resolved in the UI' do
+      before do
+        allow_any_instance_of(Gitlab::Conflict::Parser)
+          .to receive(:parse).and_raise(Gitlab::Conflict::Parser::UnmergeableFile)
+
+        get :show,
+            namespace_id: merge_request_with_conflicts.project.namespace.to_param,
+            project_id: merge_request_with_conflicts.project,
+            id: merge_request_with_conflicts.iid,
+            format: 'json'
+      end
+
+      it 'returns a 200 status code' do
+        expect(response).to have_http_status(:ok)
+      end
+
+      it 'returns JSON with a message' do
+        expect(json_response.keys).to contain_exactly('message', 'type')
+      end
+    end
+
+    context 'with valid conflicts' do
+      before do
+        get :show,
+            namespace_id: merge_request_with_conflicts.project.namespace.to_param,
+            project_id: merge_request_with_conflicts.project,
+            id: merge_request_with_conflicts.iid,
+            format: 'json'
+      end
+
+      it 'matches the schema' do
+        expect(response).to match_response_schema('conflicts')
+      end
+
+      it 'includes meta info about the MR' do
+        expect(json_response['commit_message']).to include('Merge branch')
+        expect(json_response['commit_sha']).to match(/\h{40}/)
+        expect(json_response['source_branch']).to eq(merge_request_with_conflicts.source_branch)
+        expect(json_response['target_branch']).to eq(merge_request_with_conflicts.target_branch)
+      end
+
+      it 'includes each file that has conflicts' do
+        filenames = json_response['files'].map { |file| file['new_path'] }
+
+        expect(filenames).to contain_exactly('files/ruby/popen.rb', 'files/ruby/regex.rb')
+      end
+
+      it 'splits files into sections with lines' do
+        json_response['files'].each do |file|
+          file['sections'].each do |section|
+            expect(section).to include('conflict', 'lines')
+
+            section['lines'].each do |line|
+              if section['conflict']
+                expect(line['type']).to be_in(%w(old new))
+                expect(line.values_at('old_line', 'new_line')).to contain_exactly(nil, a_kind_of(Integer))
+              else
+                if line['type'].nil?
+                  expect(line['old_line']).not_to eq(nil)
+                  expect(line['new_line']).not_to eq(nil)
+                else
+                  expect(line['type']).to eq('match')
+                  expect(line['old_line']).to eq(nil)
+                  expect(line['new_line']).to eq(nil)
+                end
+              end
+            end
+          end
+        end
+      end
+
+      it 'has unique section IDs across files' do
+        section_ids = json_response['files'].flat_map do |file|
+          file['sections'].map { |section| section['id'] }.compact
+        end
+
+        expect(section_ids.uniq).to eq(section_ids)
+      end
+    end
+  end
+
+  describe 'GET conflict_for_path' do
+    def conflict_for_path(path)
+      get :conflict_for_path,
+          namespace_id: merge_request_with_conflicts.project.namespace.to_param,
+          project_id: merge_request_with_conflicts.project,
+          id: merge_request_with_conflicts.iid,
+          old_path: path,
+          new_path: path,
+          format: 'json'
+    end
+
+    context 'when the conflicts cannot be resolved in the UI' do
+      before do
+        allow_any_instance_of(Gitlab::Conflict::Parser)
+          .to receive(:parse).and_raise(Gitlab::Conflict::Parser::UnmergeableFile)
+
+        conflict_for_path('files/ruby/regex.rb')
+      end
+
+      it 'returns a 404 status code' do
+        expect(response).to have_http_status(:not_found)
+      end
+    end
+
+    context 'when the file does not exist cannot be resolved in the UI' do
+      before do
+        conflict_for_path('files/ruby/regexp.rb')
+      end
+
+      it 'returns a 404 status code' do
+        expect(response).to have_http_status(:not_found)
+      end
+    end
+
+    context 'with an existing file' do
+      let(:path) { 'files/ruby/regex.rb' }
+
+      before do
+        conflict_for_path(path)
+      end
+
+      it 'returns a 200 status code' do
+        expect(response).to have_http_status(:ok)
+      end
+
+      it 'returns the file in JSON format' do
+        content = MergeRequests::Conflicts::ListService.new(merge_request_with_conflicts)
+                    .file_for_path(path, path)
+                    .content
+
+        expect(json_response).to include('old_path' => path,
+                                         'new_path' => path,
+                                         'blob_icon' => 'file-text-o',
+                                         'blob_path' => a_string_ending_with(path),
+                                         'blob_ace_mode' => 'ruby',
+                                         'content' => content)
+      end
+    end
+  end
+
+  context 'POST resolve_conflicts' do
+    let!(:original_head_sha) { merge_request_with_conflicts.diff_head_sha }
+
+    def resolve_conflicts(files)
+      post :resolve_conflicts,
+           namespace_id: merge_request_with_conflicts.project.namespace.to_param,
+           project_id: merge_request_with_conflicts.project,
+           id: merge_request_with_conflicts.iid,
+           format: 'json',
+           files: files,
+           commit_message: 'Commit message'
+    end
+
+    context 'with valid params' do
+      before do
+        resolved_files = [
+          {
+            'new_path' => 'files/ruby/popen.rb',
+            'old_path' => 'files/ruby/popen.rb',
+            'sections' => {
+              '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head'
+            }
+          }, {
+            'new_path' => 'files/ruby/regex.rb',
+            'old_path' => 'files/ruby/regex.rb',
+            'sections' => {
+              '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head',
+              '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin',
+              '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin'
+            }
+          }
+        ]
+
+        resolve_conflicts(resolved_files)
+      end
+
+      it 'creates a new commit on the branch' do
+        expect(original_head_sha).not_to eq(merge_request_with_conflicts.source_branch_head.sha)
+        expect(merge_request_with_conflicts.source_branch_head.message).to include('Commit message')
+      end
+
+      it 'returns an OK response' do
+        expect(response).to have_http_status(:ok)
+      end
+    end
+
+    context 'when sections are missing' do
+      before do
+        resolved_files = [
+          {
+            'new_path' => 'files/ruby/popen.rb',
+            'old_path' => 'files/ruby/popen.rb',
+            'sections' => {
+              '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head'
+            }
+          }, {
+            'new_path' => 'files/ruby/regex.rb',
+            'old_path' => 'files/ruby/regex.rb',
+            'sections' => {
+              '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head'
+            }
+          }
+        ]
+
+        resolve_conflicts(resolved_files)
+      end
+
+      it 'returns a 400 error' do
+        expect(response).to have_http_status(:bad_request)
+      end
+
+      it 'has a message with the name of the first missing section' do
+        expect(json_response['message']).to include('6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21')
+      end
+
+      it 'does not create a new commit' do
+        expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha)
+      end
+    end
+
+    context 'when files are missing' do
+      before do
+        resolved_files = [
+          {
+            'new_path' => 'files/ruby/regex.rb',
+            'old_path' => 'files/ruby/regex.rb',
+            'sections' => {
+              '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head',
+              '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin',
+              '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin'
+            }
+          }
+        ]
+
+        resolve_conflicts(resolved_files)
+      end
+
+      it 'returns a 400 error' do
+        expect(response).to have_http_status(:bad_request)
+      end
+
+      it 'has a message with the name of the missing file' do
+        expect(json_response['message']).to include('files/ruby/popen.rb')
+      end
+
+      it 'does not create a new commit' do
+        expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha)
+      end
+    end
+
+    context 'when a file has identical content to the conflict' do
+      before do
+        content = MergeRequests::Conflicts::ListService.new(merge_request_with_conflicts)
+                    .file_for_path('files/ruby/popen.rb', 'files/ruby/popen.rb')
+                    .content
+
+        resolved_files = [
+          {
+            'new_path' => 'files/ruby/popen.rb',
+            'old_path' => 'files/ruby/popen.rb',
+            'content' => content
+          }, {
+            'new_path' => 'files/ruby/regex.rb',
+            'old_path' => 'files/ruby/regex.rb',
+            'sections' => {
+              '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head',
+              '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin',
+              '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin'
+            }
+          }
+        ]
+
+        resolve_conflicts(resolved_files)
+      end
+
+      it 'returns a 400 error' do
+        expect(response).to have_http_status(:bad_request)
+      end
+
+      it 'has a message with the path of the problem file' do
+        expect(json_response['message']).to include('files/ruby/popen.rb')
+      end
+
+      it 'does not create a new commit' do
+        expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha)
+      end
+    end
+  end
+end
diff --git a/spec/controllers/projects/merge_requests/creations_controller_spec.rb b/spec/controllers/projects/merge_requests/creations_controller_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f9d8f0f5fcfe5fde0bff277cabf8158c025da48c
--- /dev/null
+++ b/spec/controllers/projects/merge_requests/creations_controller_spec.rb
@@ -0,0 +1,120 @@
+require 'spec_helper'
+
+describe Projects::MergeRequests::CreationsController do
+  let(:project) { create(:project) }
+  let(:user)    { project.owner }
+  let(:fork_project) { create(:forked_project_with_submodules) }
+
+  before do
+    fork_project.team << [user, :master]
+
+    sign_in(user)
+  end
+
+  describe 'GET new' do
+    context 'merge request that removes a submodule' do
+      render_views
+
+      it 'renders new merge request widget template' do
+        get :new,
+            namespace_id: fork_project.namespace.to_param,
+            project_id: fork_project,
+            merge_request: {
+              source_branch: 'remove-submodule',
+              target_branch: 'master'
+            }
+
+        expect(response).to be_success
+      end
+    end
+  end
+
+  describe 'GET pipelines' do
+    before do
+      create(:ci_pipeline, sha: fork_project.commit('remove-submodule').id,
+                           ref: 'remove-submodule',
+                           project: fork_project)
+    end
+
+    it 'renders JSON including serialized pipelines' do
+      get :pipelines,
+          namespace_id: fork_project.namespace.to_param,
+          project_id: fork_project,
+          merge_request: {
+            source_branch: 'remove-submodule',
+            target_branch: 'master'
+          },
+          format: :json
+
+      expect(response).to be_ok
+      expect(json_response).to have_key 'pipelines'
+      expect(json_response['pipelines']).not_to be_empty
+    end
+  end
+
+  describe 'GET diff_for_path' do
+    def diff_for_path(extra_params = {})
+      params = {
+        namespace_id: project.namespace.to_param,
+        project_id: project,
+        format: 'json'
+      }
+
+      get :diff_for_path, params.merge(extra_params)
+    end
+
+    let(:existing_path) { 'files/ruby/feature.rb' }
+
+    context 'when both branches are in the same project' do
+      it 'disables diff notes' do
+        diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_branch: 'feature', target_branch: 'master' })
+
+        expect(assigns(:diff_notes_disabled)).to be_truthy
+      end
+
+      it 'only renders the diffs for the path given' do
+        expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs|
+          expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path)
+          meth.call(diffs)
+        end
+
+        diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_branch: 'feature', target_branch: 'master' })
+      end
+    end
+
+    context 'when the source branch is in a different project to the target' do
+      let(:other_project) { create(:project) }
+
+      before do
+        other_project.team << [user, :master]
+      end
+
+      context 'when the path exists in the diff' do
+        it 'disables diff notes' do
+          diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' })
+
+          expect(assigns(:diff_notes_disabled)).to be_truthy
+        end
+
+        it 'only renders the diffs for the path given' do
+          expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs|
+            expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path)
+            meth.call(diffs)
+          end
+
+          diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' })
+        end
+      end
+
+      context 'when the path does not exist in the diff' do
+        before do
+          diff_for_path(old_path: 'files/ruby/nopen.rb', new_path: 'files/ruby/nopen.rb', merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' })
+        end
+
+        it 'returns a 404' do
+          expect(response).to have_http_status(404)
+        end
+      end
+    end
+  end
+end
diff --git a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..53fe2bdb189bcab806f35bf68b5b231fe272057b
--- /dev/null
+++ b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
@@ -0,0 +1,160 @@
+require 'spec_helper'
+
+describe Projects::MergeRequests::DiffsController do
+  let(:project) { create(:project) }
+  let(:user)    { project.owner }
+  let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
+
+  before do
+    sign_in(user)
+  end
+
+  describe 'GET show' do
+    def go(extra_params = {})
+      params = {
+        namespace_id: project.namespace.to_param,
+        project_id: project,
+        id: merge_request.iid,
+        format: 'json'
+      }
+
+      get :show, params.merge(extra_params)
+    end
+
+    context 'with default params' do
+      context 'for the same project' do
+        before do
+          go
+        end
+
+        it 'renders the diffs template to a string' do
+          expect(response).to render_template('projects/merge_requests/diffs/_diffs')
+          expect(json_response).to have_key('html')
+        end
+      end
+
+      context 'with forked projects with submodules' do
+        render_views
+
+        let(:project) { create(:project) }
+        let(:fork_project) { create(:forked_project_with_submodules) }
+        let(:merge_request) { create(:merge_request_with_diffs, source_project: fork_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) }
+
+        before do
+          fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
+          fork_project.save
+          merge_request.reload
+          go
+        end
+
+        it 'renders' do
+          expect(response).to be_success
+          expect(response.body).to have_content('Subproject commit')
+        end
+      end
+    end
+
+    context 'with ignore_whitespace_change' do
+      before do
+        go(w: 1)
+      end
+
+      it 'renders the diffs template to a string' do
+        expect(response).to render_template('projects/merge_requests/diffs/_diffs')
+        expect(json_response).to have_key('html')
+      end
+    end
+
+    context 'with view' do
+      before do
+        go(view: 'parallel')
+      end
+
+      it 'saves the preferred diff view in a cookie' do
+        expect(response.cookies['diff_view']).to eq('parallel')
+      end
+    end
+  end
+
+  describe 'GET diff_for_path' do
+    def diff_for_path(extra_params = {})
+      params = {
+        namespace_id: project.namespace.to_param,
+        project_id: project,
+        id: merge_request.iid,
+        format: 'json'
+      }
+
+      get :diff_for_path, params.merge(extra_params)
+    end
+
+    let(:existing_path) { 'files/ruby/popen.rb' }
+
+    context 'when the merge request exists' do
+      context 'when the user can view the merge request' do
+        context 'when the path exists in the diff' do
+          it 'enables diff notes' do
+            diff_for_path(old_path: existing_path, new_path: existing_path)
+
+            expect(assigns(:diff_notes_disabled)).to be_falsey
+            expect(assigns(:new_diff_note_attrs)).to eq(noteable_type: 'MergeRequest',
+                                                        noteable_id: merge_request.id)
+          end
+
+          it 'only renders the diffs for the path given' do
+            expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs|
+              expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path)
+              meth.call(diffs)
+            end
+
+            diff_for_path(old_path: existing_path, new_path: existing_path)
+          end
+        end
+
+        context 'when the path does not exist in the diff' do
+          before do
+            diff_for_path(old_path: 'files/ruby/nopen.rb', new_path: 'files/ruby/nopen.rb')
+          end
+
+          it 'returns a 404' do
+            expect(response).to have_http_status(404)
+          end
+        end
+      end
+
+      context 'when the user cannot view the merge request' do
+        before do
+          project.team.truncate
+          diff_for_path(old_path: existing_path, new_path: existing_path)
+        end
+
+        it 'returns a 404' do
+          expect(response).to have_http_status(404)
+        end
+      end
+    end
+
+    context 'when the merge request does not exist' do
+      before do
+        diff_for_path(id: merge_request.iid.succ, old_path: existing_path, new_path: existing_path)
+      end
+
+      it 'returns a 404' do
+        expect(response).to have_http_status(404)
+      end
+    end
+
+    context 'when the merge request belongs to a different project' do
+      let(:other_project) { create(:empty_project) }
+
+      before do
+        other_project.team << [user, :master]
+        diff_for_path(old_path: existing_path, new_path: existing_path, project_id: other_project)
+      end
+
+      it 'returns a 404' do
+        expect(response).to have_http_status(404)
+      end
+    end
+  end
+end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index d8a3a510f97eae2c795e3ddfbfce810ea63e8c26..6f9ce60cf753a83bd6bd4b44c35a9764acfeb7c5 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -14,53 +14,6 @@ describe Projects::MergeRequestsController do
     sign_in(user)
   end
 
-  describe 'GET new' do
-    context 'merge request that removes a submodule' do
-      render_views
-
-      let(:fork_project) { create(:forked_project_with_submodules) }
-
-      before do
-        fork_project.team << [user, :master]
-      end
-
-      context 'when rendering HTML response' do
-        it 'renders new merge request widget template' do
-          submit_new_merge_request
-
-          expect(response).to be_success
-        end
-      end
-
-      context 'when rendering JSON response' do
-        before do
-          create(:ci_pipeline, sha: fork_project.commit('remove-submodule').id,
-                               ref: 'remove-submodule',
-                               project: fork_project)
-        end
-
-        it 'renders JSON including serialized pipelines' do
-          submit_new_merge_request(format: :json)
-
-          expect(response).to be_ok
-          expect(json_response).to have_key 'pipelines'
-          expect(json_response['pipelines']).not_to be_empty
-        end
-      end
-    end
-
-    def submit_new_merge_request(format: :html)
-      get :new,
-          namespace_id: fork_project.namespace.to_param,
-          project_id: fork_project,
-          merge_request: {
-            source_branch: 'remove-submodule',
-            target_branch: 'master'
-          },
-          format: format
-    end
-  end
-
   describe 'GET commit_change_content' do
     it 'renders commit_change_content template' do
       get :commit_change_content,
@@ -497,234 +450,6 @@ describe Projects::MergeRequestsController do
     end
   end
 
-  describe 'GET diffs' do
-    def go(extra_params = {})
-      params = {
-        namespace_id: project.namespace.to_param,
-        project_id: project,
-        id: merge_request.iid
-      }
-
-      get :diffs, params.merge(extra_params)
-    end
-
-    it_behaves_like "loads labels", :diffs
-
-    context 'with default params' do
-      context 'as html' do
-        before do
-          go(format: 'html')
-        end
-
-        it 'renders the diff template' do
-          expect(response).to render_template('diffs')
-        end
-      end
-
-      context 'as json' do
-        before do
-          go(format: 'json')
-        end
-
-        it 'renders the diffs template to a string' do
-          expect(response).to render_template('projects/merge_requests/show/_diffs')
-          expect(json_response).to have_key('html')
-        end
-      end
-
-      context 'with forked projects with submodules' do
-        render_views
-
-        let(:project) { create(:project) }
-        let(:fork_project) { create(:forked_project_with_submodules) }
-        let(:merge_request) { create(:merge_request_with_diffs, source_project: fork_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) }
-
-        before do
-          fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
-          fork_project.save
-          merge_request.reload
-          go(format: 'json')
-        end
-
-        it 'renders' do
-          expect(response).to be_success
-          expect(response.body).to have_content('Subproject commit')
-        end
-      end
-    end
-
-    context 'with ignore_whitespace_change' do
-      context 'as html' do
-        before do
-          go(format: 'html', w: 1)
-        end
-
-        it 'renders the diff template' do
-          expect(response).to render_template('diffs')
-        end
-      end
-
-      context 'as json' do
-        before do
-          go(format: 'json', w: 1)
-        end
-
-        it 'renders the diffs template to a string' do
-          expect(response).to render_template('projects/merge_requests/show/_diffs')
-          expect(json_response).to have_key('html')
-        end
-      end
-    end
-
-    context 'with view' do
-      before do
-        go(view: 'parallel')
-      end
-
-      it 'saves the preferred diff view in a cookie' do
-        expect(response.cookies['diff_view']).to eq('parallel')
-      end
-    end
-  end
-
-  describe 'GET diff_for_path' do
-    def diff_for_path(extra_params = {})
-      params = {
-        namespace_id: project.namespace.to_param,
-        project_id: project
-      }
-
-      get :diff_for_path, params.merge(extra_params)
-    end
-
-    context 'when an ID param is passed' do
-      let(:existing_path) { 'files/ruby/popen.rb' }
-
-      context 'when the merge request exists' do
-        context 'when the user can view the merge request' do
-          context 'when the path exists in the diff' do
-            it 'enables diff notes' do
-              diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path)
-
-              expect(assigns(:diff_notes_disabled)).to be_falsey
-              expect(assigns(:new_diff_note_attrs)).to eq(noteable_type: 'MergeRequest',
-                                                          noteable_id: merge_request.id)
-            end
-
-            it 'only renders the diffs for the path given' do
-              expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs|
-                expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path)
-                meth.call(diffs)
-              end
-
-              diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path)
-            end
-          end
-
-          context 'when the path does not exist in the diff' do
-            before do
-              diff_for_path(id: merge_request.iid, old_path: 'files/ruby/nopen.rb', new_path: 'files/ruby/nopen.rb')
-            end
-
-            it 'returns a 404' do
-              expect(response).to have_http_status(404)
-            end
-          end
-        end
-
-        context 'when the user cannot view the merge request' do
-          before do
-            project.team.truncate
-            diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path)
-          end
-
-          it 'returns a 404' do
-            expect(response).to have_http_status(404)
-          end
-        end
-      end
-
-      context 'when the merge request does not exist' do
-        before do
-          diff_for_path(id: merge_request.iid.succ, old_path: existing_path, new_path: existing_path)
-        end
-
-        it 'returns a 404' do
-          expect(response).to have_http_status(404)
-        end
-      end
-
-      context 'when the merge request belongs to a different project' do
-        let(:other_project) { create(:empty_project) }
-
-        before do
-          other_project.team << [user, :master]
-          diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path, project_id: other_project)
-        end
-
-        it 'returns a 404' do
-          expect(response).to have_http_status(404)
-        end
-      end
-    end
-
-    context 'when source and target params are passed' do
-      let(:existing_path) { 'files/ruby/feature.rb' }
-
-      context 'when both branches are in the same project' do
-        it 'disables diff notes' do
-          diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_branch: 'feature', target_branch: 'master' })
-
-          expect(assigns(:diff_notes_disabled)).to be_truthy
-        end
-
-        it 'only renders the diffs for the path given' do
-          expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs|
-            expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path)
-            meth.call(diffs)
-          end
-
-          diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_branch: 'feature', target_branch: 'master' })
-        end
-      end
-
-      context 'when the source branch is in a different project to the target' do
-        let(:other_project) { create(:project) }
-
-        before do
-          other_project.team << [user, :master]
-        end
-
-        context 'when the path exists in the diff' do
-          it 'disables diff notes' do
-            diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' })
-
-            expect(assigns(:diff_notes_disabled)).to be_truthy
-          end
-
-          it 'only renders the diffs for the path given' do
-            expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs|
-              expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path)
-              meth.call(diffs)
-            end
-
-            diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' })
-          end
-        end
-
-        context 'when the path does not exist in the diff' do
-          before do
-            diff_for_path(old_path: 'files/ruby/nopen.rb', new_path: 'files/ruby/nopen.rb', merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' })
-          end
-
-          it 'returns a 404' do
-            expect(response).to have_http_status(404)
-          end
-        end
-      end
-    end
-  end
-
   describe 'GET commits' do
     def go(format: 'html')
       get :commits,
@@ -734,23 +459,11 @@ describe Projects::MergeRequestsController do
           format: format
     end
 
-    it_behaves_like "loads labels", :commits
+    it 'renders the commits template to a string' do
+      go format: 'json'
 
-    context 'as html' do
-      it 'renders the show template' do
-        go
-
-        expect(response).to render_template('show')
-      end
-    end
-
-    context 'as json' do
-      it 'renders the commits template to a string' do
-        go format: 'json'
-
-        expect(response).to render_template('projects/merge_requests/show/_commits')
-        expect(json_response).to have_key('html')
-      end
+      expect(response).to render_template('projects/merge_requests/_commits')
+      expect(json_response).to have_key('html')
     end
   end
 
@@ -759,106 +472,16 @@ describe Projects::MergeRequestsController 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,
-            id: merge_request.iid,
-            format: :json
-      end
-
-      it 'responds with serialized pipelines' do
-        expect(json_response).not_to be_empty
-      end
-    end
-  end
-
-  describe 'GET conflicts' do
-    context 'when the conflicts cannot be resolved in the UI' do
-      before do
-        allow_any_instance_of(Gitlab::Conflict::Parser).
-          to receive(:parse).and_raise(Gitlab::Conflict::Parser::UnmergeableFile)
-
-        get :conflicts,
-            namespace_id: merge_request_with_conflicts.project.namespace.to_param,
-            project_id: merge_request_with_conflicts.project,
-            id: merge_request_with_conflicts.iid,
-            format: 'json'
-      end
-
-      it 'returns a 200 status code' do
-        expect(response).to have_http_status(:ok)
-      end
-
-      it 'returns JSON with a message' do
-        expect(json_response.keys).to contain_exactly('message', 'type')
-      end
+      get :pipelines,
+          namespace_id: project.namespace.to_param,
+          project_id: project,
+          id: merge_request.iid,
+          format: :json
     end
 
-    context 'with valid conflicts' do
-      before do
-        get :conflicts,
-            namespace_id: merge_request_with_conflicts.project.namespace.to_param,
-            project_id: merge_request_with_conflicts.project,
-            id: merge_request_with_conflicts.iid,
-            format: 'json'
-      end
-
-      it 'matches the schema' do
-        expect(response).to match_response_schema('conflicts')
-      end
-
-      it 'includes meta info about the MR' do
-        expect(json_response['commit_message']).to include('Merge branch')
-        expect(json_response['commit_sha']).to match(/\h{40}/)
-        expect(json_response['source_branch']).to eq(merge_request_with_conflicts.source_branch)
-        expect(json_response['target_branch']).to eq(merge_request_with_conflicts.target_branch)
-      end
-
-      it 'includes each file that has conflicts' do
-        filenames = json_response['files'].map { |file| file['new_path'] }
-
-        expect(filenames).to contain_exactly('files/ruby/popen.rb', 'files/ruby/regex.rb')
-      end
-
-      it 'splits files into sections with lines' do
-        json_response['files'].each do |file|
-          file['sections'].each do |section|
-            expect(section).to include('conflict', 'lines')
-
-            section['lines'].each do |line|
-              if section['conflict']
-                expect(line['type']).to be_in(%w(old new))
-                expect(line.values_at('old_line', 'new_line')).to contain_exactly(nil, a_kind_of(Integer))
-              else
-                if line['type'].nil?
-                  expect(line['old_line']).not_to eq(nil)
-                  expect(line['new_line']).not_to eq(nil)
-                else
-                  expect(line['type']).to eq('match')
-                  expect(line['old_line']).to eq(nil)
-                  expect(line['new_line']).to eq(nil)
-                end
-              end
-            end
-          end
-        end
-      end
-
-      it 'has unique section IDs across files' do
-        section_ids = json_response['files'].flat_map do |file|
-          file['sections'].map { |section| section['id'] }.compact
-        end
-
-        expect(section_ids.uniq).to eq(section_ids)
-      end
+    it 'responds with serialized pipelines' do
+      expect(json_response).not_to be_empty
     end
   end
 
@@ -913,215 +536,6 @@ describe Projects::MergeRequestsController do
     end
   end
 
-  describe 'GET conflict_for_path' do
-    def conflict_for_path(path)
-      get :conflict_for_path,
-          namespace_id: merge_request_with_conflicts.project.namespace.to_param,
-          project_id: merge_request_with_conflicts.project,
-          id: merge_request_with_conflicts.iid,
-          old_path: path,
-          new_path: path,
-          format: 'json'
-    end
-
-    context 'when the conflicts cannot be resolved in the UI' do
-      before do
-        allow_any_instance_of(Gitlab::Conflict::Parser).
-          to receive(:parse).and_raise(Gitlab::Conflict::Parser::UnmergeableFile)
-
-        conflict_for_path('files/ruby/regex.rb')
-      end
-
-      it 'returns a 404 status code' do
-        expect(response).to have_http_status(:not_found)
-      end
-    end
-
-    context 'when the file does not exist cannot be resolved in the UI' do
-      before do
-        conflict_for_path('files/ruby/regexp.rb')
-      end
-
-      it 'returns a 404 status code' do
-        expect(response).to have_http_status(:not_found)
-      end
-    end
-
-    context 'with an existing file' do
-      let(:path) { 'files/ruby/regex.rb' }
-
-      before do
-        conflict_for_path(path)
-      end
-
-      it 'returns a 200 status code' do
-        expect(response).to have_http_status(:ok)
-      end
-
-      it 'returns the file in JSON format' do
-        content = MergeRequests::Conflicts::ListService.new(merge_request_with_conflicts).
-                    file_for_path(path, path).
-                    content
-
-        expect(json_response).to include('old_path' => path,
-                                         'new_path' => path,
-                                         'blob_icon' => 'file-text-o',
-                                         'blob_path' => a_string_ending_with(path),
-                                         'blob_ace_mode' => 'ruby',
-                                         'content' => content)
-      end
-    end
-  end
-
-  context 'POST resolve_conflicts' do
-    let!(:original_head_sha) { merge_request_with_conflicts.diff_head_sha }
-
-    def resolve_conflicts(files)
-      post :resolve_conflicts,
-           namespace_id: merge_request_with_conflicts.project.namespace.to_param,
-           project_id: merge_request_with_conflicts.project,
-           id: merge_request_with_conflicts.iid,
-           format: 'json',
-           files: files,
-           commit_message: 'Commit message'
-    end
-
-    context 'with valid params' do
-      before do
-        resolved_files = [
-          {
-            'new_path' => 'files/ruby/popen.rb',
-            'old_path' => 'files/ruby/popen.rb',
-            'sections' => {
-              '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head'
-            }
-          }, {
-            'new_path' => 'files/ruby/regex.rb',
-            'old_path' => 'files/ruby/regex.rb',
-            'sections' => {
-              '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head',
-              '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin',
-              '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin'
-            }
-          }
-        ]
-
-        resolve_conflicts(resolved_files)
-      end
-
-      it 'creates a new commit on the branch' do
-        expect(original_head_sha).not_to eq(merge_request_with_conflicts.source_branch_head.sha)
-        expect(merge_request_with_conflicts.source_branch_head.message).to include('Commit message')
-      end
-
-      it 'returns an OK response' do
-        expect(response).to have_http_status(:ok)
-      end
-    end
-
-    context 'when sections are missing' do
-      before do
-        resolved_files = [
-          {
-            'new_path' => 'files/ruby/popen.rb',
-            'old_path' => 'files/ruby/popen.rb',
-            'sections' => {
-              '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head'
-            }
-          }, {
-            'new_path' => 'files/ruby/regex.rb',
-            'old_path' => 'files/ruby/regex.rb',
-            'sections' => {
-              '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head'
-            }
-          }
-        ]
-
-        resolve_conflicts(resolved_files)
-      end
-
-      it 'returns a 400 error' do
-        expect(response).to have_http_status(:bad_request)
-      end
-
-      it 'has a message with the name of the first missing section' do
-        expect(json_response['message']).to include('6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21')
-      end
-
-      it 'does not create a new commit' do
-        expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha)
-      end
-    end
-
-    context 'when files are missing' do
-      before do
-        resolved_files = [
-          {
-            'new_path' => 'files/ruby/regex.rb',
-            'old_path' => 'files/ruby/regex.rb',
-            'sections' => {
-              '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head',
-              '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin',
-              '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin'
-            }
-          }
-        ]
-
-        resolve_conflicts(resolved_files)
-      end
-
-      it 'returns a 400 error' do
-        expect(response).to have_http_status(:bad_request)
-      end
-
-      it 'has a message with the name of the missing file' do
-        expect(json_response['message']).to include('files/ruby/popen.rb')
-      end
-
-      it 'does not create a new commit' do
-        expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha)
-      end
-    end
-
-    context 'when a file has identical content to the conflict' do
-      before do
-        content = MergeRequests::Conflicts::ListService.new(merge_request_with_conflicts).
-                    file_for_path('files/ruby/popen.rb', 'files/ruby/popen.rb').
-                    content
-
-        resolved_files = [
-          {
-            'new_path' => 'files/ruby/popen.rb',
-            'old_path' => 'files/ruby/popen.rb',
-            'content' => content
-          }, {
-            'new_path' => 'files/ruby/regex.rb',
-            'old_path' => 'files/ruby/regex.rb',
-            'sections' => {
-              '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head',
-              '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin',
-              '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin'
-            }
-          }
-        ]
-
-        resolve_conflicts(resolved_files)
-      end
-
-      it 'returns a 400 error' do
-        expect(response).to have_http_status(:bad_request)
-      end
-
-      it 'has a message with the path of the problem file' do
-        expect(json_response['message']).to include('files/ruby/popen.rb')
-      end
-
-      it 'does not create a new commit' do
-        expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha)
-      end
-    end
-  end
-
   describe 'POST assign_related_issues' do
     let(:issue1) { create(:issue, project: project) }
     let(:issue2) { create(:issue, project: project) }
@@ -1153,9 +567,9 @@ describe Projects::MergeRequestsController do
     end
 
     it 'calls MergeRequests::AssignIssuesService' do
-      expect(MergeRequests::AssignIssuesService).to receive(:new).
-        with(project, user, merge_request: merge_request).
-        and_return(double(execute: { count: 1 }))
+      expect(MergeRequests::AssignIssuesService).to receive(:new)
+        .with(project, user, merge_request: merge_request)
+        .and_return(double(execute: { count: 1 }))
 
       post_assign_issues
     end
diff --git a/spec/controllers/projects/pages_domains_controller_spec.rb b/spec/controllers/projects/pages_domains_controller_spec.rb
index 33853c4b9d02e06a4a9da1b1d30b57c22bd76de3..920189be3818a9a7c5b12210e82f57544dead4c6 100644
--- a/spec/controllers/projects/pages_domains_controller_spec.rb
+++ b/spec/controllers/projects/pages_domains_controller_spec.rb
@@ -46,7 +46,7 @@ describe Projects::PagesDomainsController do
         post(:create, request_params.merge(pages_domain: pages_domain_params))
       end.to change { PagesDomain.count }.by(1)
 
-      expect(response).to redirect_to(namespace_project_pages_path(project.namespace, project))
+      expect(response).to redirect_to(project_pages_path(project))
     end
   end
 
@@ -56,7 +56,7 @@ describe Projects::PagesDomainsController do
         delete(:destroy, request_params.merge(id: pages_domain.domain))
       end.to change { PagesDomain.count }.by(-1)
 
-      expect(response).to redirect_to(namespace_project_pages_path(project.namespace, project))
+      expect(response).to redirect_to(project_pages_path(project))
     end
   end
 
diff --git a/spec/controllers/projects/pipeline_schedules_controller_spec.rb b/spec/controllers/projects/pipeline_schedules_controller_spec.rb
index f8f95dd9bc873cb2462b750fde96d907ba7a835d..a8c44d5c313ec4a678272a4d25e8ddab98e0e9ee 100644
--- a/spec/controllers/projects/pipeline_schedules_controller_spec.rb
+++ b/spec/controllers/projects/pipeline_schedules_controller_spec.rb
@@ -84,4 +84,57 @@ describe Projects::PipelineSchedulesController do
       end
     end
   end
+
+  describe 'security' do
+    include AccessMatchersForController
+
+    describe 'GET edit' do
+      it { expect { go }.to be_allowed_for(:admin) }
+      it { expect { go }.to be_allowed_for(:owner).of(project) }
+      it { expect { go }.to be_allowed_for(:master).of(project) }
+      it { expect { go }.to be_allowed_for(:developer).of(project) }
+      it { expect { go }.to be_denied_for(:reporter).of(project) }
+      it { expect { go }.to be_denied_for(:guest).of(project) }
+      it { expect { go }.to be_denied_for(:user) }
+      it { expect { go }.to be_denied_for(:external) }
+      it { expect { go }.to be_denied_for(:visitor) }
+
+      def go
+        get :edit, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id
+      end
+    end
+
+    describe 'GET take_ownership' do
+      it { expect { go }.to be_allowed_for(:admin) }
+      it { expect { go }.to be_allowed_for(:owner).of(project) }
+      it { expect { go }.to be_allowed_for(:master).of(project) }
+      it { expect { go }.to be_allowed_for(:developer).of(project) }
+      it { expect { go }.to be_denied_for(:reporter).of(project) }
+      it { expect { go }.to be_denied_for(:guest).of(project) }
+      it { expect { go }.to be_denied_for(:user) }
+      it { expect { go }.to be_denied_for(:external) }
+      it { expect { go }.to be_denied_for(:visitor) }
+
+      def go
+        post :take_ownership, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id
+      end
+    end
+
+    describe 'PUT update' do
+      it { expect { go }.to be_allowed_for(:admin) }
+      it { expect { go }.to be_allowed_for(:owner).of(project) }
+      it { expect { go }.to be_allowed_for(:master).of(project) }
+      it { expect { go }.to be_allowed_for(:developer).of(project) }
+      it { expect { go }.to be_denied_for(:reporter).of(project) }
+      it { expect { go }.to be_denied_for(:guest).of(project) }
+      it { expect { go }.to be_denied_for(:user) }
+      it { expect { go }.to be_denied_for(:external) }
+      it { expect { go }.to be_denied_for(:visitor) }
+
+      def go
+        put :update, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id,
+                     schedule: { description: 'a' }
+      end
+    end
+  end
 end
diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb
index f2b59ba82caf9bb69d9ba3587baafbbf6fd6adf2..548ec8f487ffef2bebcca5cd42bcf04d54aae311 100644
--- a/spec/controllers/projects/project_members_controller_spec.rb
+++ b/spec/controllers/projects/project_members_controller_spec.rb
@@ -9,7 +9,7 @@ describe Projects::ProjectMembersController do
       get :index, namespace_id: project.namespace, project_id: project
 
       expect(response).to have_http_status(302)
-      expect(response.location).to include namespace_project_settings_members_path(project.namespace, project)
+      expect(response.location).to include project_settings_members_path(project)
     end
   end
 
@@ -50,7 +50,7 @@ describe Projects::ProjectMembersController do
                       access_level: Gitlab::Access::GUEST
 
         expect(response).to set_flash.to 'Users were successfully added.'
-        expect(response).to redirect_to(namespace_project_settings_members_path(project.namespace, project))
+        expect(response).to redirect_to(project_settings_members_path(project))
       end
 
       it 'adds no user to members' do
@@ -62,7 +62,7 @@ describe Projects::ProjectMembersController do
                       access_level: Gitlab::Access::GUEST
 
         expect(response).to set_flash.to 'Message'
-        expect(response).to redirect_to(namespace_project_settings_members_path(project.namespace, project))
+        expect(response).to redirect_to(project_settings_members_path(project))
       end
     end
   end
@@ -111,7 +111,7 @@ describe Projects::ProjectMembersController do
                            id: member
 
           expect(response).to redirect_to(
-            namespace_project_settings_members_path(project.namespace, project)
+            project_settings_members_path(project)
           )
           expect(project.members).not_to include member
         end
@@ -183,7 +183,7 @@ describe Projects::ProjectMembersController do
                          project_id: project
 
           expect(response).to set_flash.to 'Your access request to the project has been withdrawn.'
-          expect(response).to redirect_to(namespace_project_path(project.namespace, project))
+          expect(response).to redirect_to(project_path(project))
           expect(project.requesters).to be_empty
           expect(project.users).not_to include user
         end
@@ -202,7 +202,7 @@ describe Projects::ProjectMembersController do
 
       expect(response).to set_flash.to 'Your request for access has been queued for review.'
       expect(response).to redirect_to(
-        namespace_project_path(project.namespace, project)
+        project_path(project)
       )
       expect(project.requesters.exists?(user_id: user)).to be_truthy
       expect(project.users).not_to include user
@@ -253,7 +253,7 @@ describe Projects::ProjectMembersController do
                                         id: member
 
           expect(response).to redirect_to(
-            namespace_project_settings_members_path(project.namespace, project)
+            project_settings_members_path(project)
           )
           expect(project.members).to include member
         end
@@ -290,7 +290,7 @@ describe Projects::ProjectMembersController do
         expect(project.team_members).to include member
         expect(response).to set_flash.to 'Successfully imported'
         expect(response).to redirect_to(
-          namespace_project_settings_members_path(project.namespace, project)
+          project_settings_members_path(project)
         )
       end
     end
diff --git a/spec/controllers/projects/prometheus_controller_spec.rb b/spec/controllers/projects/prometheus_controller_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..eddf7275975968e82402b063167e102899c9581b
--- /dev/null
+++ b/spec/controllers/projects/prometheus_controller_spec.rb
@@ -0,0 +1,59 @@
+require('spec_helper')
+
+describe Projects::PrometheusController do
+  let(:user) { create(:user) }
+  let!(:project) { create(:empty_project) }
+
+  let(:prometheus_service) { double('prometheus_service') }
+
+  before do
+    allow(controller).to receive(:project).and_return(project)
+    allow(project).to receive(:prometheus_service).and_return(prometheus_service)
+
+    project.add_master(user)
+    sign_in(user)
+  end
+
+  describe 'GET #active_metrics' do
+    context 'when prometheus metrics are enabled' do
+      context 'when data is not present' do
+        before do
+          allow(prometheus_service).to receive(:matched_metrics).and_return({})
+        end
+
+        it 'returns no content response' do
+          get :active_metrics, project_params(format: :json)
+
+          expect(response).to have_http_status(204)
+        end
+      end
+
+      context 'when data is available' do
+        let(:sample_response) { { some_data: 1 } }
+
+        before do
+          allow(prometheus_service).to receive(:matched_metrics).and_return(sample_response)
+        end
+
+        it 'returns no content response' do
+          get :active_metrics, project_params(format: :json)
+
+          expect(response).to have_http_status(200)
+          expect(json_response).to eq(sample_response.deep_stringify_keys)
+        end
+      end
+
+      context 'when requesting non json response' do
+        it 'returns not found response' do
+          get :active_metrics, project_params
+
+          expect(response).to have_http_status(404)
+        end
+      end
+    end
+  end
+
+  def project_params(opts = {})
+    opts.reverse_merge(namespace_id: project.namespace, project_id: project)
+  end
+end
diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb
index 952071af57ff432556f8150ab6193e799e255028..b4eaab29fed50645b4ac663ef28a6dfe5c82a7ac 100644
--- a/spec/controllers/projects/raw_controller_spec.rb
+++ b/spec/controllers/projects/raw_controller_spec.rb
@@ -15,8 +15,8 @@ describe Projects::RawController do
 
         expect(response).to have_http_status(200)
         expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
-        expect(response.header['Content-Disposition']).
-            to eq('inline')
+        expect(response.header['Content-Disposition'])
+            .to eq('inline')
         expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
       end
     end
@@ -88,8 +88,8 @@ describe Projects::RawController do
 
           expect(response).to have_http_status(200)
           expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
-          expect(response.header['Content-Disposition']).
-              to eq('inline')
+          expect(response.header['Content-Disposition'])
+              .to eq('inline')
           expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
         end
       end
diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb
index 23b463c0082a6ce4c35c09586468a556102e7404..5a9d8a75f3e6880e1c8852cf75b0e47eee07a57d 100644
--- a/spec/controllers/projects/services_controller_spec.rb
+++ b/spec/controllers/projects/services_controller_spec.rb
@@ -67,8 +67,8 @@ describe Projects::ServicesController do
         put :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, service: service_params
 
         expect(response.status).to eq(200)
-        expect(JSON.parse(response.body)).
-          to eq('error' => true, 'message' => 'Test failed.', 'service_response' => 'Bad test')
+        expect(JSON.parse(response.body))
+          .to eq('error' => true, 'message' => 'Test failed.', 'service_response' => 'Bad test')
       end
     end
   end
@@ -79,7 +79,7 @@ describe Projects::ServicesController do
         put :update,
           namespace_id: project.namespace.id, project_id: project.id, id: service.id, service: { active: true }
 
-        expect(response).to redirect_to(namespace_project_settings_integrations_path(project.namespace, project))
+        expect(response).to redirect_to(project_settings_integrations_path(project))
         expect(flash[:notice]).to eq 'HipChat activated.'
       end
     end
diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb
index 2434f822c6f0ef6e50f588d6f9737eb193dae7e6..cc444f317979291e68b66c05820ce9fefffbe4c7 100644
--- a/spec/controllers/projects/snippets_controller_spec.rb
+++ b/spec/controllers/projects/snippets_controller_spec.rb
@@ -103,21 +103,21 @@ describe Projects::SnippetsController 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)
+          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 { 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)
+          expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }
+            .to change { SpamLog.count }.by(1)
         end
 
         it 'renders :new with recaptcha disabled' do
@@ -148,7 +148,7 @@ describe Projects::SnippetsController do
                            { spam_log_id: spam_logs.last.id,
                              recaptcha_verification: true })
 
-            expect(response).to redirect_to(Snippet.last)
+            expect(response).to redirect_to(project_snippet_path(project, Snippet.last))
           end
         end
       end
@@ -183,8 +183,8 @@ describe Projects::SnippetsController do
         let(:visibility_level) { Snippet::PRIVATE }
 
         it 'updates the snippet' do
-          expect { update_snippet(title: 'Foo') }.
-            to change { snippet.reload.title }.to('Foo')
+          expect { update_snippet(title: 'Foo') }
+            .to change { snippet.reload.title }.to('Foo')
         end
       end
 
@@ -192,13 +192,13 @@ describe Projects::SnippetsController do
         let(:visibility_level) { Snippet::PUBLIC }
 
         it 'rejects the shippet' do
-          expect { update_snippet(title: 'Foo') }.
-            not_to change { snippet.reload.title }
+          expect { update_snippet(title: 'Foo') }
+            .not_to change { snippet.reload.title }
         end
 
         it 'creates a spam log' do
-          expect { update_snippet(title: 'Foo') }.
-            to change { SpamLog.count }.by(1)
+          expect { update_snippet(title: 'Foo') }
+            .to change { SpamLog.count }.by(1)
         end
 
         it 'renders :edit with recaptcha disabled' do
@@ -228,7 +228,7 @@ describe Projects::SnippetsController do
                                      { spam_log_id: spam_logs.last.id,
                                        recaptcha_verification: true })
 
-            expect(response).to redirect_to(snippet)
+            expect(response).to redirect_to(project_snippet_path(project, snippet))
           end
         end
       end
@@ -237,13 +237,13 @@ describe Projects::SnippetsController do
         let(:visibility_level) { Snippet::PRIVATE }
 
         it 'rejects the shippet' do
-          expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }.
-            not_to change { snippet.reload.title }
+          expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }
+            .not_to change { snippet.reload.title }
         end
 
         it 'creates a spam log' do
-          expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }.
-            to change { SpamLog.count }.by(1)
+          expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }
+            .to change { SpamLog.count }.by(1)
         end
 
         it 'renders :edit with recaptcha disabled' do
@@ -273,7 +273,7 @@ describe Projects::SnippetsController do
                                      { spam_log_id: spam_logs.last.id,
                                        recaptcha_verification: true })
 
-            expect(response).to redirect_to(snippet)
+            expect(response).to redirect_to(project_snippet_path(project, snippet))
           end
         end
       end
diff --git a/spec/controllers/projects/tree_controller_spec.rb b/spec/controllers/projects/tree_controller_spec.rb
index a43dad5756d23df2a123ec9b63e5f69c33d28756..16cd2e076e5e0bc14dbaccd0cf8255d0d06d9d33 100644
--- a/spec/controllers/projects/tree_controller_spec.rb
+++ b/spec/controllers/projects/tree_controller_spec.rb
@@ -82,8 +82,8 @@ describe Projects::TreeController do
       let(:id) { 'master/README.md' }
       it 'redirects' do
         redirect_url = "/#{project.path_with_namespace}/blob/master/README.md"
-        expect(subject).
-          to redirect_to(redirect_url)
+        expect(subject)
+          .to redirect_to(redirect_url)
       end
     end
   end
@@ -106,8 +106,8 @@ describe Projects::TreeController do
       let(:branch_name) { 'master-test'}
 
       it 'redirects to the new directory' do
-        expect(subject).
-            to redirect_to("/#{project.path_with_namespace}/tree/#{branch_name}/#{path}")
+        expect(subject)
+            .to redirect_to("/#{project.path_with_namespace}/tree/#{branch_name}/#{path}")
         expect(flash[:notice]).to eq('The directory has been successfully created.')
       end
     end
@@ -117,8 +117,8 @@ describe Projects::TreeController do
       let(:branch_name) { 'master'}
 
       it 'does not allow overwriting of existing files' do
-        expect(subject).
-            to redirect_to("/#{project.path_with_namespace}/tree/master")
+        expect(subject)
+            .to redirect_to("/#{project.path_with_namespace}/tree/master")
         expect(flash[:alert]).to eq('A file with this name already exists')
       end
     end
diff --git a/spec/controllers/projects/variables_controller_spec.rb b/spec/controllers/projects/variables_controller_spec.rb
index 1ecfe48475cbaaa17fc989dea634d818b165dc57..a0ecc7566537a59db820586eaab62b9f2ea09ecc 100644
--- a/spec/controllers/projects/variables_controller_spec.rb
+++ b/spec/controllers/projects/variables_controller_spec.rb
@@ -16,7 +16,7 @@ describe Projects::VariablesController do
                       variable: { key: "one", value: "two" }
 
         expect(flash[:notice]).to include 'Variables were successfully updated.'
-        expect(response).to redirect_to(namespace_project_settings_ci_cd_path(project.namespace, project))
+        expect(response).to redirect_to(project_settings_ci_cd_path(project))
       end
     end
 
@@ -44,7 +44,7 @@ describe Projects::VariablesController do
                       id: variable.id, variable: { key: variable.key, value: 'two' }
 
         expect(flash[:notice]).to include 'Variable was successfully updated.'
-        expect(response).to redirect_to(namespace_project_variables_path(project.namespace, project))
+        expect(response).to redirect_to(project_variables_path(project))
       end
 
       it 'renders the action #show if the variable key is invalid' do
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 240a81367d02ef77b031d07de764a5b2b31cfc72..f96fe7ad5cba2ac52e840fa89ee63dfefed09e6e 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -482,7 +482,7 @@ describe ProjectsController do
         it 'redirects to the canonical path (testing non-show action)' do
           get :refs, namespace_id: 'foo', id: 'bar'
 
-          expect(response).to redirect_to(refs_namespace_project_path(namespace_id: public_project.namespace, id: public_project))
+          expect(response).to redirect_to(refs_project_path(public_project))
           expect(controller).to set_flash[:notice].to(project_moved_message(redirect_route, public_project))
         end
       end
diff --git a/spec/controllers/sent_notifications_controller_spec.rb b/spec/controllers/sent_notifications_controller_spec.rb
index 0cc8a3b68eb589e8826b6665e6675ef4afb704f9..7340a4e16c02043b9c1fa73825fc38f5532b1c02 100644
--- a/spec/controllers/sent_notifications_controller_spec.rb
+++ b/spec/controllers/sent_notifications_controller_spec.rb
@@ -87,8 +87,8 @@ describe SentNotificationsController, type: :controller do
         end
 
         it 'redirects to the issue page' do
-          expect(response).
-            to redirect_to(namespace_project_issue_path(project.namespace, project, issue))
+          expect(response)
+            .to redirect_to(project_issue_path(project, issue))
         end
       end
 
@@ -113,8 +113,8 @@ describe SentNotificationsController, type: :controller do
         end
 
         it 'redirects to the merge request page' do
-          expect(response).
-            to redirect_to(namespace_project_merge_request_path(project.namespace, project, merge_request))
+          expect(response)
+            .to redirect_to(project_merge_request_path(project, merge_request))
         end
       end
     end
diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb
index 03f4b0ba3437c73f601d9ed5b480df353475b4e7..bf922260b2f7a61d2a82767b00a5851f698be114 100644
--- a/spec/controllers/sessions_controller_spec.rb
+++ b/spec/controllers/sessions_controller_spec.rb
@@ -9,8 +9,8 @@ describe SessionsController do
     context 'when auto sign-in is enabled' do
       before do
         stub_omniauth_setting(auto_sign_in_with_provider: :saml)
-        allow(controller).to receive(:omniauth_authorize_path).with(:user, :saml).
-          and_return('/saml')
+        allow(controller).to receive(:omniauth_authorize_path).with(:user, :saml)
+          .and_return('/saml')
       end
 
       context 'and no auto_sign_in param is passed' do
@@ -89,8 +89,8 @@ describe SessionsController do
       context 'remember_me field' do
         it 'sets a remember_user_token cookie when enabled' do
           allow(controller).to receive(:find_user).and_return(user)
-          expect(controller).
-            to receive(:remember_me).with(user).and_call_original
+          expect(controller)
+            .to receive(:remember_me).with(user).and_call_original
 
           authenticate_2fa(remember_me: '1', otp_attempt: user.current_otp)
 
@@ -228,8 +228,8 @@ describe SessionsController do
         it 'sets a remember_user_token cookie when enabled' do
           allow(U2fRegistration).to receive(:authenticate).and_return(true)
           allow(controller).to receive(:find_user).and_return(user)
-          expect(controller).
-            to receive(:remember_me).with(user).and_call_original
+          expect(controller)
+            .to receive(:remember_me).with(user).and_call_original
 
           authenticate_2fa_u2f(remember_me: '1', login: user.username, device_response: "{}")
 
@@ -262,8 +262,8 @@ describe SessionsController do
 
     it 'redirects correctly for referer on same host with params' do
       search_path = '/search?search=seed_project'
-      allow(controller.request).to receive(:referer).
-        and_return('http://%{host}%{path}' % { host: Gitlab.config.gitlab.host, path: search_path })
+      allow(controller.request).to receive(:referer)
+        .and_return('http://%{host}%{path}' % { host: Gitlab.config.gitlab.host, path: search_path })
 
       get(:new, redirect_to_referer: :yes)
 
diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb
index b1339b2a185320c9c7981cd3569d8dbefc74367c..15416a89017fa8ade7b262eb558ed319ef731029 100644
--- a/spec/controllers/snippets_controller_spec.rb
+++ b/spec/controllers/snippets_controller_spec.rb
@@ -222,20 +222,20 @@ describe SnippetsController do
 
       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)
+          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 { create_snippet(visibility_level: Snippet::PUBLIC) }
+            .not_to change { Snippet.count }
         end
 
         it 'creates a spam log' do
-          expect { create_snippet(visibility_level: Snippet::PUBLIC) }.
-            to change { SpamLog.count }.by(1)
+          expect { create_snippet(visibility_level: Snippet::PUBLIC) }
+            .to change { SpamLog.count }.by(1)
         end
 
         it 'renders :new with recaptcha disabled' do
@@ -296,8 +296,8 @@ describe SnippetsController do
         let(:visibility_level) { Snippet::PRIVATE }
 
         it 'updates the snippet' do
-          expect { update_snippet(title: 'Foo') }.
-            to change { snippet.reload.title }.to('Foo')
+          expect { update_snippet(title: 'Foo') }
+            .to change { snippet.reload.title }.to('Foo')
         end
       end
 
@@ -305,13 +305,13 @@ describe SnippetsController do
         let(:visibility_level) { Snippet::PRIVATE }
 
         it 'rejects the snippet' do
-          expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }.
-            not_to change { snippet.reload.title }
+          expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }
+            .not_to change { snippet.reload.title }
         end
 
         it 'creates a spam log' do
-          expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }.
-            to change { SpamLog.count }.by(1)
+          expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }
+            .to change { SpamLog.count }.by(1)
         end
 
         it 'renders :edit with recaptcha disabled' do
@@ -341,7 +341,7 @@ describe SnippetsController do
                                      { spam_log_id: spam_logs.last.id,
                                        recaptcha_verification: true })
 
-            expect(response).to redirect_to(snippet)
+            expect(response).to redirect_to(snippet_path(snippet))
           end
         end
       end
@@ -350,13 +350,13 @@ describe SnippetsController do
         let(:visibility_level) { Snippet::PUBLIC }
 
         it 'rejects the shippet' do
-          expect { update_snippet(title: 'Foo') }.
-            not_to change { snippet.reload.title }
+          expect { update_snippet(title: 'Foo') }
+            .not_to change { snippet.reload.title }
         end
 
         it 'creates a spam log' do
-          expect { update_snippet(title: 'Foo') }.
-            to change { SpamLog.count }.by(1)
+          expect { update_snippet(title: 'Foo') }
+            .to change { SpamLog.count }.by(1)
         end
 
         it 'renders :edit with recaptcha disabled' do
diff --git a/spec/factories/application_settings.rb b/spec/factories/application_settings.rb
new file mode 100644
index 0000000000000000000000000000000000000000..aef65e724c280d0dba6cf6e43ae54bbb4144d5d6
--- /dev/null
+++ b/spec/factories/application_settings.rb
@@ -0,0 +1,4 @@
+FactoryGirl.define do
+  factory :application_setting do
+  end
+end
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 0cc498f0ce9c914d9b1310f38fff9b7948cf3f16..a77f01ecb0022382f6420cede7c3a81e17e03281 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -207,7 +207,8 @@ FactoryGirl.define do
             cache: {
                 key: 'cache_key',
                 untracked: false,
-                paths: ['vendor/*']
+                paths: ['vendor/*'],
+                policy: 'pull-push'
             }
         }
       end
diff --git a/spec/factories/personal_snippets.rb b/spec/factories/personal_snippets.rb
deleted file mode 100644
index 0f13b2c10209c72dcfb7903acddbca801057fddf..0000000000000000000000000000000000000000
--- a/spec/factories/personal_snippets.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-FactoryGirl.define do
-  factory :personal_snippet, parent: :snippet, class: :PersonalSnippet do
-  end
-end
diff --git a/spec/factories/project_snippets.rb b/spec/factories/project_snippets.rb
deleted file mode 100644
index e0fe1b36fd35329cf220422f8afc58c374b7cf11..0000000000000000000000000000000000000000
--- a/spec/factories/project_snippets.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-FactoryGirl.define do
-  factory :project_snippet, parent: :snippet, class: :ProjectSnippet do
-    project factory: :empty_project
-  end
-end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index e17e50db143f455067242e36c4b7135a7beea35e..1bb2db11e7f1f89466b7751e04a5e2a4ad548353 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -118,8 +118,8 @@ FactoryGirl.define do
       builds_access_level = [evaluator.builds_access_level, evaluator.repository_access_level].min
       merge_requests_access_level = [evaluator.merge_requests_access_level, evaluator.repository_access_level].min
 
-      project.project_feature.
-        update_attributes!(
+      project.project_feature
+        .update_attributes!(
           wiki_access_level: evaluator.wiki_access_level,
           builds_access_level: builds_access_level,
           snippets_access_level: evaluator.snippets_access_level,
@@ -220,7 +220,7 @@ FactoryGirl.define do
         active: true,
         properties: {
           'project_url' => 'http://redmine/projects/project_name_in_redmine',
-          'issues_url' => "http://redmine/#{project.id}/project_name_in_redmine/:id",
+          'issues_url' => 'http://redmine/projects/project_name_in_redmine/issues/:id',
           'new_issue_url' => 'http://redmine/projects/project_name_in_redmine/issues/new'
         }
       )
diff --git a/spec/factories/services.rb b/spec/factories/services.rb
index e7366a7fd1c36c9fdfb0085ba05ddaf792273f54..30bc25cf88a4109be352ce5155252a8a0a7a3299 100644
--- a/spec/factories/services.rb
+++ b/spec/factories/services.rb
@@ -25,6 +25,14 @@ FactoryGirl.define do
     })
   end
 
+  factory :prometheus_service do
+    project factory: :empty_project
+    active true
+    properties({
+      api_url: 'https://prometheus.example.com/'
+    })
+  end
+
   factory :jira_service do
     project factory: :empty_project
     active true
diff --git a/spec/factories/snippets.rb b/spec/factories/snippets.rb
index 388f662e6e53fbff4bfe49cac120fe0c8233f8f3..f6ce99d50f9a7f8317a8bc6105ac6c9963c755fb 100644
--- a/spec/factories/snippets.rb
+++ b/spec/factories/snippets.rb
@@ -18,4 +18,11 @@ FactoryGirl.define do
       visibility_level Snippet::PRIVATE
     end
   end
+
+  factory :project_snippet, parent: :snippet, class: :ProjectSnippet do
+    project factory: :empty_project
+  end
+
+  factory :personal_snippet, parent: :snippet, class: :PersonalSnippet do
+  end
 end
diff --git a/spec/features/abuse_report_spec.rb b/spec/features/abuse_report_spec.rb
index 1e11fb756b23953c3408238ffb51fc65ff7ed77e..b88e801c3d741c3506d73368e85b7f9ad1aa76e6 100644
--- a/spec/features/abuse_report_spec.rb
+++ b/spec/features/abuse_report_spec.rb
@@ -4,7 +4,7 @@ feature 'Abuse reports', feature: true do
   let(:another_user) { create(:user) }
 
   before do
-    login_as :user
+    gitlab_sign_in :user
   end
 
   scenario 'Report abuse' do
@@ -12,7 +12,7 @@ feature 'Abuse reports', feature: true do
 
     click_link 'Report abuse'
 
-    fill_in 'abuse_report_message', with: 'This user send spam'
+    fill_in 'abuse_report_message', with: 'This user sends spam'
     click_button 'Send report'
 
     expect(page).to have_content 'Thank you for your report'
diff --git a/spec/features/admin/admin_abuse_reports_spec.rb b/spec/features/admin/admin_abuse_reports_spec.rb
index 340884fc986835dd6133470d1582bc178bb463b9..3a6e356b0b07a288a99fb4be5373694727c6feb9 100644
--- a/spec/features/admin/admin_abuse_reports_spec.rb
+++ b/spec/features/admin/admin_abuse_reports_spec.rb
@@ -5,7 +5,7 @@ describe "Admin::AbuseReports", feature: true, js: true  do
 
   context 'as an admin' do
     before do
-      login_as :admin
+      gitlab_sign_in :admin
     end
 
     describe 'if a user has been reported for abuse' do
diff --git a/spec/features/admin/admin_active_tab_spec.rb b/spec/features/admin/admin_active_tab_spec.rb
index 16064d60ce2c24d67b6714b30426e2d6735351af..c74336d8221203da8b2550b6e839c72a7ce3863d 100644
--- a/spec/features/admin/admin_active_tab_spec.rb
+++ b/spec/features/admin/admin_active_tab_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 RSpec.describe 'admin active tab' do
   before do
-    login_as :admin
+    gitlab_sign_in :admin
   end
 
   shared_examples 'page has active tab' do |title|
diff --git a/spec/features/admin/admin_appearance_spec.rb b/spec/features/admin/admin_appearance_spec.rb
index 595366ce35278feac0effa3f71a67eb3b821e567..d8fd4319328e9999d80756b8d7f926ea4e0dceff 100644
--- a/spec/features/admin/admin_appearance_spec.rb
+++ b/spec/features/admin/admin_appearance_spec.rb
@@ -4,7 +4,7 @@ feature 'Admin Appearance', feature: true do
   let!(:appearance) { create(:appearance) }
 
   scenario 'Create new appearance' do
-    login_as :admin
+    gitlab_sign_in :admin
     visit admin_appearances_path
 
     fill_in 'appearance_title', with: 'MyCompany'
@@ -20,7 +20,7 @@ feature 'Admin Appearance', feature: true do
   end
 
   scenario 'Preview appearance' do
-    login_as :admin
+    gitlab_sign_in :admin
 
     visit admin_appearances_path
     click_link "Preview"
@@ -34,7 +34,7 @@ feature 'Admin Appearance', feature: true do
   end
 
   scenario 'Appearance logo' do
-    login_as :admin
+    gitlab_sign_in :admin
     visit admin_appearances_path
 
     attach_file(:appearance_logo, logo_fixture)
@@ -46,7 +46,7 @@ feature 'Admin Appearance', feature: true do
   end
 
   scenario 'Header logos' do
-    login_as :admin
+    gitlab_sign_in :admin
     visit admin_appearances_path
 
     attach_file(:appearance_header_logo, logo_fixture)
diff --git a/spec/features/admin/admin_broadcast_messages_spec.rb b/spec/features/admin/admin_broadcast_messages_spec.rb
index d6c63f66a9bf0e0d4c63db02ae625a0e810ed290..da063bf7b744542336caaf903fbe326d4940a4a2 100644
--- a/spec/features/admin/admin_broadcast_messages_spec.rb
+++ b/spec/features/admin/admin_broadcast_messages_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 feature 'Admin Broadcast Messages', feature: true do
   before do
-    login_as :admin
+    gitlab_sign_in :admin
     create(:broadcast_message, :expired, message: 'Migration to new server')
     visit admin_broadcast_messages_path
   end
diff --git a/spec/features/admin/admin_browse_spam_logs_spec.rb b/spec/features/admin/admin_browse_spam_logs_spec.rb
index bee57472270f8e8cc7912a876f2430c3cd58c758..d9c4fc686b138687ac347ea107125b3376c78996 100644
--- a/spec/features/admin/admin_browse_spam_logs_spec.rb
+++ b/spec/features/admin/admin_browse_spam_logs_spec.rb
@@ -4,7 +4,7 @@ describe 'Admin browse spam logs' do
   let!(:spam_log) { create(:spam_log, description: 'abcde ' * 20) }
 
   before do
-    login_as :admin
+    gitlab_sign_in :admin
   end
 
   scenario 'Browse spam logs' do
diff --git a/spec/features/admin/admin_browses_logs_spec.rb b/spec/features/admin/admin_browses_logs_spec.rb
index d880f3f07db3bf51fc2a7009426dc7b61a4a5e36..c734a2ef16daef47ec20001b0a8560c0e64c468d 100644
--- a/spec/features/admin/admin_browses_logs_spec.rb
+++ b/spec/features/admin/admin_browses_logs_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 describe 'Admin browses logs' do
   before do
-    login_as :admin
+    gitlab_sign_in :admin
   end
 
   it 'shows available log files' do
diff --git a/spec/features/admin/admin_builds_spec.rb b/spec/features/admin/admin_builds_spec.rb
index 999ce3611b505f651c26b7b12e53a8e2a5b4bee5..e767081f3e58d91099607b41a1719c22214d3977 100644
--- a/spec/features/admin/admin_builds_spec.rb
+++ b/spec/features/admin/admin_builds_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 describe 'Admin Builds' do
   before do
-    login_as :admin
+    gitlab_sign_in :admin
   end
 
   describe 'GET /admin/builds' do
diff --git a/spec/features/admin/admin_cohorts_spec.rb b/spec/features/admin/admin_cohorts_spec.rb
index dd14ffdb2ceb3b304091dc145de81b31e8682ef6..952e5475213a9ec578e7ea8a888de847bcfa7678 100644
--- a/spec/features/admin/admin_cohorts_spec.rb
+++ b/spec/features/admin/admin_cohorts_spec.rb
@@ -2,7 +2,7 @@ require 'rails_helper'
 
 feature 'Admin cohorts page', feature: true do
   before do
-    login_as :admin
+    gitlab_sign_in :admin
   end
 
   scenario 'See users count per month' do
diff --git a/spec/features/admin/admin_conversational_development_index_spec.rb b/spec/features/admin/admin_conversational_development_index_spec.rb
index 739ab907a29adb14afdeecc376a26dfa9654e5f7..b484677a6dfb48f2f78f4d3bc75a949a7948c17f 100644
--- a/spec/features/admin/admin_conversational_development_index_spec.rb
+++ b/spec/features/admin/admin_conversational_development_index_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 describe 'Admin Conversational Development Index' do
   before do
-    login_as :admin
+    gitlab_sign_in :admin
   end
 
   context 'when usage ping is disabled' do
diff --git a/spec/features/admin/admin_deploy_keys_spec.rb b/spec/features/admin/admin_deploy_keys_spec.rb
index 5f5fa4e932afafae92a28d2ba109e811c3c205c8..81cddd03f80bf72a45daa7fb587b2af33b411233 100644
--- a/spec/features/admin/admin_deploy_keys_spec.rb
+++ b/spec/features/admin/admin_deploy_keys_spec.rb
@@ -5,7 +5,7 @@ RSpec.describe 'admin deploy keys', type: :feature do
   let!(:another_deploy_key) { create(:another_deploy_key, public: true) }
 
   before do
-    login_as(:admin)
+    gitlab_sign_in(:admin)
   end
 
   it 'show all public deploy keys' do
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 e8e080ce3e26ba52fc3c18fd170249e4320d89ae..063d54270bdcda8c1550e830e48e78414871a462 100644
--- a/spec/features/admin/admin_disables_git_access_protocol_spec.rb
+++ b/spec/features/admin/admin_disables_git_access_protocol_spec.rb
@@ -8,7 +8,7 @@ feature 'Admin disables Git access protocol', feature: true do
 
   background do
     stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
-    login_as(admin)
+    gitlab_sign_in(admin)
   end
 
   context 'with HTTP disabled' do
@@ -51,7 +51,7 @@ feature 'Admin disables Git access protocol', feature: true do
   end
 
   def visit_project
-    visit namespace_project_path(project.namespace, project)
+    visit project_path(project)
   end
 
   def disable_http_protocol
diff --git a/spec/features/admin/admin_disables_two_factor_spec.rb b/spec/features/admin/admin_disables_two_factor_spec.rb
index 71be66303d236041e90b026472f9348cd26f3f2a..5437da299797cd2f565a4c857b7cdb903ae10f1b 100644
--- a/spec/features/admin/admin_disables_two_factor_spec.rb
+++ b/spec/features/admin/admin_disables_two_factor_spec.rb
@@ -2,7 +2,7 @@ require 'rails_helper'
 
 feature 'Admin disables 2FA for a user', feature: true do
   scenario 'successfully', js: true do
-    login_as(:admin)
+    gitlab_sign_in(:admin)
     user = create(:user, :two_factor)
 
     edit_user(user)
@@ -17,7 +17,7 @@ feature 'Admin disables 2FA for a user', feature: true do
   end
 
   scenario 'for a user without 2FA enabled' do
-    login_as(:admin)
+    gitlab_sign_in(:admin)
     user = create(:user)
 
     edit_user(user)
diff --git a/spec/features/admin/admin_groups_spec.rb b/spec/features/admin/admin_groups_spec.rb
index cf9d7bca2558562fc45292c8b9df0b061bdeac94..8b0fafc5f07003960ac5b78a5fb8a025bfd0258d 100644
--- a/spec/features/admin/admin_groups_spec.rb
+++ b/spec/features/admin/admin_groups_spec.rb
@@ -6,7 +6,7 @@ feature 'Admin Groups', feature: true do
   let(:internal) { Gitlab::VisibilityLevel::INTERNAL }
   let(:user) { create :user }
   let!(:group) { create :group }
-  let!(:current_user) { login_as :admin }
+  let!(:current_user) { gitlab_sign_in :admin }
 
   before do
     stub_application_setting(default_group_visibility: internal)
diff --git a/spec/features/admin/admin_health_check_spec.rb b/spec/features/admin/admin_health_check_spec.rb
index 523afa2318fe805fd2ab7d9df8914609e388502f..75093aa4167c8bffee6875283670dbedc0977162 100644
--- a/spec/features/admin/admin_health_check_spec.rb
+++ b/spec/features/admin/admin_health_check_spec.rb
@@ -5,7 +5,7 @@ feature "Admin Health Check", feature: true do
 
   before do
     stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
-    login_as :admin
+    gitlab_sign_in :admin
   end
 
   describe '#show' do
diff --git a/spec/features/admin/admin_hook_logs_spec.rb b/spec/features/admin/admin_hook_logs_spec.rb
index 5b67f4de6ac309aed016dc5254d5b32cdc155165..ec80c420c7938f7cee54647898d2d75ca7bab799 100644
--- a/spec/features/admin/admin_hook_logs_spec.rb
+++ b/spec/features/admin/admin_hook_logs_spec.rb
@@ -6,7 +6,7 @@ feature 'Admin::HookLogs', feature: true do
   let(:hook_log) { create(:web_hook_log, web_hook: system_hook, internal_error_message: 'some error') }
 
   before do
-    login_as :admin
+    gitlab_sign_in :admin
   end
 
   scenario 'show list of hook logs' do
diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb
index 80f7ec43c060c76f2a31ae7f424c065f019a6313..c07c21bd6a188f935887aad2422c31b1c28fe20c 100644
--- a/spec/features/admin/admin_hooks_spec.rb
+++ b/spec/features/admin/admin_hooks_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe 'Admin::Hooks', feature: true do
   before do
     @project = create(:project)
-    login_as :admin
+    gitlab_sign_in :admin
 
     @system_hook = create(:system_hook)
   end
diff --git a/spec/features/admin/admin_labels_spec.rb b/spec/features/admin/admin_labels_spec.rb
index a9251db13e543b7ad2dd8ccb2ed8f4a13d187ac9..bb40918bd22ed23eeacaa106104d1134b46a2691 100644
--- a/spec/features/admin/admin_labels_spec.rb
+++ b/spec/features/admin/admin_labels_spec.rb
@@ -5,7 +5,7 @@ RSpec.describe 'admin issues labels' do
   let!(:feature_label) { Label.create(title: 'feature', template: true) }
 
   before do
-    login_as :admin
+    gitlab_sign_in :admin
   end
 
   describe 'list' do
diff --git a/spec/features/admin/admin_manage_applications_spec.rb b/spec/features/admin/admin_manage_applications_spec.rb
index 0079125889b9d331f6d0612cb72fa20d5d636329..ae41267e5fc0491d619c5505c5ad095137d5c221 100644
--- a/spec/features/admin/admin_manage_applications_spec.rb
+++ b/spec/features/admin/admin_manage_applications_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 RSpec.describe 'admin manage applications', feature: true do
   before do
-    login_as :admin
+    gitlab_sign_in :admin
   end
 
   it do
diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb
index 9d205104ebecdf4b7bdac92a1a3130ee3afe17d5..c6488cea798e450ba0ef3ebb4c96872ab6e070d7 100644
--- a/spec/features/admin/admin_projects_spec.rb
+++ b/spec/features/admin/admin_projects_spec.rb
@@ -6,7 +6,7 @@ describe "Admin::Projects", feature: true  do
   let(:user) { create :user }
   let!(:project) { create(:project) }
   let!(:current_user) do
-    login_as :admin
+    gitlab_sign_in :admin
   end
 
   describe "GET /admin/projects" do
@@ -42,7 +42,7 @@ describe "Admin::Projects", feature: true  do
     end
 
     it do
-      expect(current_path).to eq admin_namespace_project_path(project.namespace, project)
+      expect(current_path).to eq admin_project_path(project)
     end
 
     it "has project info" do
@@ -57,12 +57,12 @@ describe "Admin::Projects", feature: true  do
     before do
       create(:group, name: 'Web')
 
-      allow_any_instance_of(Projects::TransferService).
-        to receive(:move_uploads_to_new_namespace).and_return(true)
+      allow_any_instance_of(Projects::TransferService)
+        .to receive(:move_uploads_to_new_namespace).and_return(true)
     end
 
     it 'transfers project to group web', js: true do
-      visit admin_namespace_project_path(project.namespace, project)
+      visit admin_project_path(project)
 
       click_button 'Search for Namespace'
       click_link 'group: web'
@@ -79,7 +79,7 @@ describe "Admin::Projects", feature: true  do
     end
 
     it 'adds admin a to a project as developer', js: true do
-      visit namespace_project_project_members_path(project.namespace, project)
+      visit project_project_members_path(project)
 
       page.within '.users-project-form' do
         select2(current_user.id, from: '#user_ids', multiple: true)
@@ -102,7 +102,7 @@ describe "Admin::Projects", feature: true  do
     end
 
     it 'removes admin from the project' do
-      visit namespace_project_project_members_path(project.namespace, project)
+      visit project_project_members_path(project)
 
       page.within '.content-list' do
         expect(page).to have_content(current_user.name)
diff --git a/spec/features/admin/admin_requests_profiles_spec.rb b/spec/features/admin/admin_requests_profiles_spec.rb
index e8ecb70306baa03621a2a78db943a3deea4de91f..2bfe401521be9d39a178cd362955ebdacc9e4694 100644
--- a/spec/features/admin/admin_requests_profiles_spec.rb
+++ b/spec/features/admin/admin_requests_profiles_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe 'Admin::RequestsProfilesController', feature: true do
   before do
     FileUtils.mkdir_p(Gitlab::RequestProfiler::PROFILES_DIR)
-    login_as(:admin)
+    gitlab_sign_in(:admin)
   end
 
   after do
diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb
index bc11b090fdb67f942a2ef1293b61fc15da78688c..6ad2d456b932747f1a21a8d57dec66d8601f096c 100644
--- a/spec/features/admin/admin_runners_spec.rb
+++ b/spec/features/admin/admin_runners_spec.rb
@@ -5,35 +5,58 @@ describe "Admin Runners" do
 
   before do
     stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
-    login_as :admin
+    gitlab_sign_in :admin
   end
 
   describe "Runners page" do
-    before do
-      runner = FactoryGirl.create(:ci_runner, contacted_at: Time.now)
-      pipeline = FactoryGirl.create(:ci_pipeline)
-      FactoryGirl.create(:ci_build, pipeline: pipeline, runner_id: runner.id)
-      visit admin_runners_path
-    end
+    let(:pipeline) { create(:ci_pipeline) }
+
+    context "when there are runners" do
+      before do
+        runner = FactoryGirl.create(:ci_runner, contacted_at: Time.now)
+        FactoryGirl.create(:ci_build, pipeline: pipeline, runner_id: runner.id)
+        visit admin_runners_path
+      end
+
+      it 'has all necessary texts' do
+        expect(page).to have_text "To register a new Runner"
+        expect(page).to have_text "Runners with last contact more than a minute ago: 1"
+      end
+
+      describe 'search' do
+        before do
+          FactoryGirl.create :ci_runner, description: 'runner-foo'
+          FactoryGirl.create :ci_runner, description: 'runner-bar'
+        end
+
+        it 'shows correct runner when description matches' do
+          search_form = find('#runners-search')
+          search_form.fill_in 'search', with: 'runner-foo'
+          search_form.click_button 'Search'
+
+          expect(page).to have_content("runner-foo")
+          expect(page).not_to have_content("runner-bar")
+        end
+
+        it 'shows no runner when description does not match' do
+          search_form = find('#runners-search')
+          search_form.fill_in 'search', with: 'runner-baz'
+          search_form.click_button 'Search'
 
-    it 'has all necessary texts' do
-      expect(page).to have_text "To register a new Runner"
-      expect(page).to have_text "Runners with last contact more than a minute ago: 1"
+          expect(page).to have_text 'No runners found'
+        end
+      end
     end
 
-    describe 'search' do
+    context "when there are no runners" do
       before do
-        FactoryGirl.create :ci_runner, description: 'runner-foo'
-        FactoryGirl.create :ci_runner, description: 'runner-bar'
-
-        search_form = find('#runners-search')
-        search_form.fill_in 'search', with: 'runner-foo'
-        search_form.click_button 'Search'
+        visit admin_runners_path
       end
 
-      it 'shows correct runner' do
-        expect(page).to have_content("runner-foo")
-        expect(page).not_to have_content("runner-bar")
+      it 'has all necessary texts including no runner message' do
+        expect(page).to have_text "To register a new Runner"
+        expect(page).to have_text "Runners with last contact more than a minute ago: 0"
+        expect(page).to have_text 'No runners found'
       end
     end
   end
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index 27bc25be5801d3c61c65953fdf08d979de5a8aeb..59a50ff9264afb8e633a7e2f2f658c0f3f6d11a5 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -5,7 +5,7 @@ feature 'Admin updates settings', feature: true do
 
   before do
     stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
-    login_as :admin
+    gitlab_sign_in :admin
     visit admin_application_settings_path
   end
 
@@ -16,6 +16,19 @@ feature 'Admin updates settings', feature: true do
     expect(page).to have_content "Application settings saved successfully"
   end
 
+  scenario 'Uncheck all restricted visibility levels' do
+    find('#application_setting_visibility_level_0').set(false)
+    find('#application_setting_visibility_level_10').set(false)
+    find('#application_setting_visibility_level_20').set(false)
+
+    click_button 'Save'
+
+    expect(page).to have_content "Application settings saved successfully"
+    expect(find('#application_setting_visibility_level_0')).not_to be_checked
+    expect(find('#application_setting_visibility_level_10')).not_to be_checked
+    expect(find('#application_setting_visibility_level_20')).not_to be_checked
+  end
+
   scenario 'Change application settings' do
     uncheck 'Gravatar enabled'
     fill_in 'Home page URL', with: 'https://about.gitlab.com/'
diff --git a/spec/features/admin/admin_system_info_spec.rb b/spec/features/admin/admin_system_info_spec.rb
index 1548234788668c6df091677f0329c52a0be32b0e..4efc7f0eb4813ca4fc03504e898d88119eaa36fd 100644
--- a/spec/features/admin/admin_system_info_spec.rb
+++ b/spec/features/admin/admin_system_info_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 describe 'Admin System Info' do
   before do
-    login_as :admin
+    gitlab_sign_in :admin
   end
 
   describe 'GET /admin/system_info' do
diff --git a/spec/features/admin/admin_users_impersonation_tokens_spec.rb b/spec/features/admin/admin_users_impersonation_tokens_spec.rb
index 849ec829f754aad33c8c78025573df950284398c..231c094c91d749542763dca2338dda902cbf8cb5 100644
--- a/spec/features/admin/admin_users_impersonation_tokens_spec.rb
+++ b/spec/features/admin/admin_users_impersonation_tokens_spec.rb
@@ -13,7 +13,7 @@ describe 'Admin > Users > Impersonation Tokens', feature: true, js: true do
   end
 
   before do
-    login_as(admin)
+    gitlab_sign_in(admin)
   end
 
   describe "token creation" do
diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb
index f72651667eee5bdb514936bb54861ebe5a3e3c04..6dbc697642fcbc6381e6c7c53b48396cf4d336fe 100644
--- a/spec/features/admin/admin_users_spec.rb
+++ b/spec/features/admin/admin_users_spec.rb
@@ -5,7 +5,7 @@ describe "Admin::Users", feature: true do
     create(:omniauth_user, provider: 'twitter', extern_uid: '123456')
   end
 
-  let!(:current_user) { login_as :admin }
+  let!(:current_user) { gitlab_sign_in :admin }
 
   describe "GET /admin/users" do
     before do
@@ -78,10 +78,10 @@ describe "Admin::Users", feature: true do
     it "applies defaults to user" do
       click_button "Create user"
       user = User.find_by(username: 'bang')
-      expect(user.projects_limit).
-        to eq(Gitlab.config.gitlab.default_projects_limit)
-      expect(user.can_create_group).
-        to eq(Gitlab.config.gitlab.default_can_create_group)
+      expect(user.projects_limit)
+        .to eq(Gitlab.config.gitlab.default_projects_limit)
+      expect(user.can_create_group)
+        .to eq(Gitlab.config.gitlab.default_can_create_group)
     end
 
     it "creates user with valid data" do
diff --git a/spec/features/admin/admin_uses_repository_checks_spec.rb b/spec/features/admin/admin_uses_repository_checks_spec.rb
index ab5c42365fe6aa759706093da42ab306aaff4d57..5be0e2b2f170cf074dc7d0736247b43872ca230a 100644
--- a/spec/features/admin/admin_uses_repository_checks_spec.rb
+++ b/spec/features/admin/admin_uses_repository_checks_spec.rb
@@ -5,7 +5,7 @@ feature 'Admin uses repository checks', feature: true do
 
   before do
     stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
-    login_as :admin
+    gitlab_sign_in :admin
   end
 
   scenario 'to trigger a single check' do
@@ -43,6 +43,6 @@ feature 'Admin uses repository checks', feature: true do
   end
 
   def visit_admin_project_page(project)
-    visit admin_namespace_project_path(project.namespace, project)
+    visit admin_project_path(project)
   end
 end
diff --git a/spec/features/atom/dashboard_spec.rb b/spec/features/atom/dashboard_spec.rb
index 1df058b023cb0eaad21f679bb1dfd51fe06e6eb2..2f4bb45d74b990f84dd325934faaeba8cd567e5c 100644
--- a/spec/features/atom/dashboard_spec.rb
+++ b/spec/features/atom/dashboard_spec.rb
@@ -35,8 +35,8 @@ describe "Dashboard Feed", feature: true  do
       end
 
       it "has issue comment event" do
-        expect(body).
-          to have_content("#{user.name} commented on issue ##{issue.iid}")
+        expect(body)
+          .to have_content("#{user.name} commented on issue ##{issue.iid}")
       end
     end
   end
diff --git a/spec/features/atom/issues_spec.rb b/spec/features/atom/issues_spec.rb
index a61231ea254ff27a12ff40217c0f13f05b4f87b3..f7d170a7bf6654b2cb2efe46aa0e4864348b0dd6 100644
--- a/spec/features/atom/issues_spec.rb
+++ b/spec/features/atom/issues_spec.rb
@@ -15,11 +15,11 @@ describe 'Issues Feed', feature: true  do
 
     context 'when authenticated' do
       it 'renders atom feed' do
-        login_with user
-        visit namespace_project_issues_path(project.namespace, project, :atom)
+        gitlab_sign_in user
+        visit project_issues_path(project, :atom)
 
-        expect(response_headers['Content-Type']).
-          to have_content('application/atom+xml')
+        expect(response_headers['Content-Type'])
+          .to have_content('application/atom+xml')
         expect(body).to have_selector('title', text: "#{project.name} issues")
         expect(body).to have_selector('author email', text: issue.author_public_email)
         expect(body).to have_selector('assignees assignee email', text: issue.assignees.first.public_email)
@@ -30,11 +30,11 @@ describe 'Issues Feed', feature: true  do
 
     context 'when authenticated via private token' do
       it 'renders atom feed' do
-        visit namespace_project_issues_path(project.namespace, project, :atom,
+        visit project_issues_path(project, :atom,
                                             private_token: user.private_token)
 
-        expect(response_headers['Content-Type']).
-          to have_content('application/atom+xml')
+        expect(response_headers['Content-Type'])
+          .to have_content('application/atom+xml')
         expect(body).to have_selector('title', text: "#{project.name} issues")
         expect(body).to have_selector('author email', text: issue.author_public_email)
         expect(body).to have_selector('assignees assignee email', text: issue.assignees.first.public_email)
@@ -45,11 +45,11 @@ describe 'Issues Feed', feature: true  do
 
     context 'when authenticated via RSS token' do
       it 'renders atom feed' do
-        visit namespace_project_issues_path(project.namespace, project, :atom,
+        visit project_issues_path(project, :atom,
                                             rss_token: user.rss_token)
 
-        expect(response_headers['Content-Type']).
-          to have_content('application/atom+xml')
+        expect(response_headers['Content-Type'])
+          .to have_content('application/atom+xml')
         expect(body).to have_selector('title', text: "#{project.name} issues")
         expect(body).to have_selector('author email', text: issue.author_public_email)
         expect(body).to have_selector('assignees assignee email', text: issue.assignees.first.public_email)
@@ -59,7 +59,7 @@ describe 'Issues Feed', feature: true  do
     end
 
     it "renders atom feed with url parameters for project issues" do
-      visit namespace_project_issues_path(project.namespace, project,
+      visit project_issues_path(project,
                                           :atom, rss_token: user.rss_token, state: 'opened', assignee_id: user.id)
 
       link = find('link[type="application/atom+xml"]')
diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb
index fae5aaa52bd7ca3ee83b7c51759a2c372ac238e2..44ae7204bcf349ebc1e60557535ce1126e69ebd8 100644
--- a/spec/features/atom/users_spec.rb
+++ b/spec/features/atom/users_spec.rb
@@ -55,8 +55,8 @@ describe "User Feed", feature: true  do
       end
 
       it 'has issue comment event' do
-        expect(body).
-          to have_content("#{safe_name} commented on issue ##{issue.iid}")
+        expect(body)
+          .to have_content("#{safe_name} commented on issue ##{issue.iid}")
       end
 
       it 'has XHTML summaries in issue descriptions' do
diff --git a/spec/features/auto_deploy_spec.rb b/spec/features/auto_deploy_spec.rb
index 1cf7396bbaca1f9b5a97cf09d4799caf3ae684c0..3536d59bb0868927d1fe8eb86ffc6e383dd6e243 100644
--- a/spec/features/auto_deploy_spec.rb
+++ b/spec/features/auto_deploy_spec.rb
@@ -7,7 +7,7 @@ describe 'Auto deploy' do
   before do
     create :kubernetes_service, project: project
     project.team << [user, :master]
-    login_as user
+    gitlab_sign_in user
   end
 
   context 'when no deployment service is active' do
@@ -16,7 +16,7 @@ describe 'Auto deploy' do
     end
 
     it 'does not show a button to set up auto deploy' do
-      visit namespace_project_path(project.namespace, project)
+      visit project_path(project)
       expect(page).to have_no_content('Set up auto deploy')
     end
   end
@@ -24,7 +24,7 @@ describe 'Auto deploy' do
   context 'when a deployment service is active' do
     before do
       project.kubernetes_service.update!(active: true)
-      visit namespace_project_path(project.namespace, project)
+      visit project_path(project)
     end
 
     it 'shows a button to set up auto deploy' do
diff --git a/spec/features/boards/add_issues_modal_spec.rb b/spec/features/boards/add_issues_modal_spec.rb
index 2b8edac4f10fa4e5f621c1fd2f80385ca8d04f10..eeb63f3f81a0001ca1fbdbbba25936cf66a4e4e9 100644
--- a/spec/features/boards/add_issues_modal_spec.rb
+++ b/spec/features/boards/add_issues_modal_spec.rb
@@ -14,14 +14,14 @@ describe 'Issue Boards add issue modal', :feature, :js do
   before do
     project.team << [user, :master]
 
-    login_as(user)
+    gitlab_sign_in(user)
 
-    visit namespace_project_board_path(project.namespace, project, board)
+    visit project_board_path(project, board)
     wait_for_requests
   end
 
   it 'resets filtered search state' do
-    visit namespace_project_board_path(project.namespace, project, board, search: 'testing')
+    visit project_board_path(project, board, search: 'testing')
 
     wait_for_requests
 
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index 968cc9d9c8078af03d6cc53343cd29864567c2ff..1f69758117973bb1ad12ecc970441e897f79e3df 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -12,12 +12,12 @@ describe 'Issue Boards', feature: true, js: true do
     project.team << [user, :master]
     project.team << [user2, :master]
 
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   context 'no lists' do
     before do
-      visit namespace_project_board_path(project.namespace, project, board)
+      visit project_board_path(project, board)
       wait_for_requests
       expect(page).to have_selector('.board', count: 3)
     end
@@ -81,7 +81,7 @@ describe 'Issue Boards', feature: true, js: true do
     let!(:issue9) { create(:labeled_issue, project: project, labels: [planning, testing, bug, accepting], relative_position: 1) }
 
     before do
-      visit namespace_project_board_path(project.namespace, project, board)
+      visit project_board_path(project, board)
 
       wait_for_requests
 
@@ -158,7 +158,7 @@ describe 'Issue Boards', feature: true, js: true do
         create(:labeled_issue, project: project, labels: [planning])
       end
 
-      visit namespace_project_board_path(project.namespace, project, board)
+      visit project_board_path(project, board)
       wait_for_requests
 
       page.within(find('.board:nth-child(2)')) do
@@ -247,13 +247,13 @@ describe 'Issue Boards', feature: true, js: true do
       end
 
       it 'issue moves from closed' do
-        drag(list_from_index: 3, list_to_index: 2)
+        drag(list_from_index: 2, list_to_index: 3)
 
         wait_for_board_cards(2, 8)
-        wait_for_board_cards(3, 3)
-        wait_for_board_cards(4, 0)
+        wait_for_board_cards(3, 1)
+        wait_for_board_cards(4, 2)
 
-        expect(find('.board:nth-child(3)')).to have_content(issue8.title)
+        expect(find('.board:nth-child(4)')).to have_content(issue8.title)
       end
 
       context 'issue card' do
@@ -507,7 +507,7 @@ describe 'Issue Boards', feature: true, js: true do
 
   context 'keyboard shortcuts' do
     before do
-      visit namespace_project_board_path(project.namespace, project, board)
+      visit project_board_path(project, board)
       wait_for_requests
     end
 
@@ -519,8 +519,8 @@ describe 'Issue Boards', feature: true, js: true do
 
   context 'signed out user' do
     before do
-      logout
-      visit namespace_project_board_path(project.namespace, project, board)
+      gitlab_sign_out
+      visit project_board_path(project, board)
       wait_for_requests
     end
 
@@ -542,9 +542,9 @@ describe 'Issue Boards', feature: true, js: true do
 
     before do
       project.team << [user_guest, :guest]
-      logout
-      login_as(user_guest)
-      visit namespace_project_board_path(project.namespace, project, board)
+      gitlab_sign_out
+      gitlab_sign_in(user_guest)
+      visit project_board_path(project, board)
       wait_for_requests
     end
 
diff --git a/spec/features/boards/issue_ordering_spec.rb b/spec/features/boards/issue_ordering_spec.rb
index 1c289993e281a18d928565d42645799fbd102ad6..62693fb3d11a38835d9c153f60d33d6b116a3c61 100644
--- a/spec/features/boards/issue_ordering_spec.rb
+++ b/spec/features/boards/issue_ordering_spec.rb
@@ -15,14 +15,14 @@ describe 'Issue Boards', :feature, :js do
   before do
     project.team << [user, :master]
 
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   context 'un-ordered issues' do
     let!(:issue4) { create(:labeled_issue, project: project, labels: [label]) }
 
     before do
-      visit namespace_project_board_path(project.namespace, project, board)
+      visit project_board_path(project, board)
       wait_for_requests
 
       expect(page).to have_selector('.board', count: 3)
@@ -47,7 +47,7 @@ describe 'Issue Boards', :feature, :js do
 
   context 'ordering in list' do
     before do
-      visit namespace_project_board_path(project.namespace, project, board)
+      visit project_board_path(project, board)
       wait_for_requests
 
       expect(page).to have_selector('.board', count: 3)
@@ -110,7 +110,7 @@ describe 'Issue Boards', :feature, :js do
     let!(:issue6) { create(:labeled_issue, project: project, title: 'testing 3', labels: [label2], relative_position: 1.0) }
 
     before do
-      visit namespace_project_board_path(project.namespace, project, board)
+      visit project_board_path(project, board)
       wait_for_requests
 
       expect(page).to have_selector('.board', count: 4)
diff --git a/spec/features/boards/keyboard_shortcut_spec.rb b/spec/features/boards/keyboard_shortcut_spec.rb
index c2167ba12cd6f4c1723a2fe848456cbebdadd84d..34ae6c9d81d3a272a3d853154b93fa9328972b65 100644
--- a/spec/features/boards/keyboard_shortcut_spec.rb
+++ b/spec/features/boards/keyboard_shortcut_spec.rb
@@ -6,9 +6,9 @@ describe 'Issue Boards shortcut', feature: true, js: true do
   before do
     create(:board, project: project)
 
-    login_as :admin
+    gitlab_sign_in :admin
 
-    visit namespace_project_path(project.namespace, project)
+    visit project_path(project)
   end
 
   it 'takes user to issue board index' do
diff --git a/spec/features/boards/modal_filter_spec.rb b/spec/features/boards/modal_filter_spec.rb
index b6de6143354b22d8f112c0a7b6ea47215e834bb7..40d1191a597610ebef8a7977d34f194c8b9e494e 100644
--- a/spec/features/boards/modal_filter_spec.rb
+++ b/spec/features/boards/modal_filter_spec.rb
@@ -12,7 +12,7 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
   before do
     project.team << [user, :master]
 
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   it 'shows empty state when no results found' do
@@ -202,7 +202,7 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
   end
 
   def visit_board
-    visit namespace_project_board_path(project.namespace, project, board)
+    visit project_board_path(project, board)
     wait_for_requests
 
     click_button('Add issues')
diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb
index 7ba60247587eba7092e16e7e40324d97c9e9c7a9..fa9d8b3f33d40bc9ae06d7cd1921bd3973667986 100644
--- a/spec/features/boards/new_issue_spec.rb
+++ b/spec/features/boards/new_issue_spec.rb
@@ -10,9 +10,9 @@ describe 'Issue Boards new issue', feature: true, js: true do
     before do
       project.team << [user, :master]
 
-      login_as(user)
+      gitlab_sign_in(user)
 
-      visit namespace_project_board_path(project.namespace, project, board)
+      visit project_board_path(project, board)
       wait_for_requests
 
       expect(page).to have_selector('.board', count: 3)
@@ -83,7 +83,7 @@ describe 'Issue Boards new issue', feature: true, js: true do
 
   context 'unauthorized user' do
     before do
-      visit namespace_project_board_path(project.namespace, project, board)
+      visit project_board_path(project, board)
       wait_for_requests
     end
 
diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb
index 235e48997073d95037587b4d4b08bb73f6b02999..f96ceffbc7dd8a48f8b10c576e0113ae6ee6f202 100644
--- a/spec/features/boards/sidebar_spec.rb
+++ b/spec/features/boards/sidebar_spec.rb
@@ -20,9 +20,9 @@ describe 'Issue Boards', feature: true, js: true do
 
     project.team << [user, :master]
 
-    login_as(user)
+    gitlab_sign_in(user)
 
-    visit namespace_project_board_path(project.namespace, project, board)
+    visit project_board_path(project, board)
     wait_for_requests
   end
 
@@ -79,6 +79,22 @@ describe 'Issue Boards', feature: true, js: true do
     end
   end
 
+  it 'does not show remove button for backlog or closed issues' do
+    create(:issue, project: project)
+    create(:issue, :closed, project: project)
+
+    visit project_board_path(project, board)
+    wait_for_requests
+
+    click_card(find('.board:nth-child(1)').first('.card'))
+
+    expect(find('.issue-boards-sidebar')).not_to have_button 'Remove from board'
+
+    click_card(find('.board:nth-child(3)').first('.card'))
+
+    expect(find('.issue-boards-sidebar')).not_to have_button 'Remove from board'
+  end
+
   context 'assignee' do
     it 'updates the issues assignee' do
       click_card(card)
diff --git a/spec/features/boards/sub_group_project_spec.rb b/spec/features/boards/sub_group_project_spec.rb
index 4cd05010a9326f4d22242df3406159a9e3961893..ddff4737563cd2e79e59533a4fad88dbe0bec337 100644
--- a/spec/features/boards/sub_group_project_spec.rb
+++ b/spec/features/boards/sub_group_project_spec.rb
@@ -13,9 +13,9 @@ describe 'Sub-group project issue boards', :feature, :js do
   before do
     project.add_master(user)
 
-    login_as(user)
+    gitlab_sign_in(user)
 
-    visit namespace_project_board_path(project.namespace, project, board)
+    visit project_board_path(project, board)
     wait_for_requests
   end
 
diff --git a/spec/features/calendar_spec.rb b/spec/features/calendar_spec.rb
index 1b6d8439f92778d816f7f7c54cc7b9f258cfa284..b2e72fc7dee4af29b0d5d962f4c51778244869df 100644
--- a/spec/features/calendar_spec.rb
+++ b/spec/features/calendar_spec.rb
@@ -68,7 +68,7 @@ feature 'Contributions Calendar', :feature, :js do
   end
 
   before do
-    login_as user
+    gitlab_sign_in user
   end
 
   describe 'calendar day selection' do
diff --git a/spec/features/ci_lint_spec.rb b/spec/features/ci_lint_spec.rb
index 3ebc432206a0049e9dc12cca291b2f583cf6d467..de16ee3e567d2514b4edc686182b16c7c640c7b6 100644
--- a/spec/features/ci_lint_spec.rb
+++ b/spec/features/ci_lint_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 describe 'CI Lint', js: true do
   before do
-    login_as :user
+    gitlab_sign_in :user
   end
 
   describe 'YAML parsing' do
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index 2772f05982a0db9b555ffe58d01b4e7bf475bbe4..fb1e47994ef4e3fac2ee3ee3abfd23e7245d1116 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -4,10 +4,11 @@ describe 'Commits' do
   include CiStatusHelper
 
   let(:project) { create(:project, :repository) }
+  let(:user) { create(:user) }
 
   describe 'CI' do
     before do
-      login_as :user
+      sign_in(user)
       stub_ci_pipeline_to_return_yaml_file
     end
 
@@ -27,7 +28,7 @@ describe 'Commits' do
       let!(:status) { create(:generic_commit_status, pipeline: pipeline) }
 
       before do
-        project.team << [@user, :reporter]
+        project.team << [user, :reporter]
       end
 
       describe 'Commit builds' do
@@ -52,7 +53,7 @@ describe 'Commits' do
 
       context 'when logged as developer' do
         before do
-          project.team << [@user, :developer]
+          project.team << [user, :developer]
         end
 
         describe 'Project commits' do
@@ -65,7 +66,7 @@ describe 'Commits' do
           end
 
           before do
-            visit namespace_project_commits_path(project.namespace, project, :master)
+            visit project_commits_path(project, :master)
           end
 
           it 'shows correct build status from default branch' do
@@ -146,7 +147,7 @@ describe 'Commits' do
 
       context "when logged as reporter" do
         before do
-          project.team << [@user, :reporter]
+          project.team << [user, :reporter]
           build.update_attributes(artifacts_file: artifacts_file)
           visit ci_status_path(pipeline)
         end
@@ -187,12 +188,11 @@ describe 'Commits' do
 
   context 'viewing commits for a branch' do
     let(:branch_name) { 'master' }
-    let(:user) { create(:user) }
 
     before do
       project.team << [user, :master]
-      login_with(user)
-      visit namespace_project_commits_path(project.namespace, project, branch_name)
+      sign_in(user)
+      visit project_commits_path(project, branch_name)
     end
 
     it 'includes the committed_date for each commit' do
diff --git a/spec/features/container_registry_spec.rb b/spec/features/container_registry_spec.rb
index fa7adbe71ea073060383af5c10dbdc6aea66aa09..55ef1ef29bd4a4b784a671246372cccaa0ec6d8f 100644
--- a/spec/features/container_registry_spec.rb
+++ b/spec/features/container_registry_spec.rb
@@ -9,7 +9,7 @@ describe "Container Registry" do
   end
 
   before do
-    login_as(user)
+    gitlab_sign_in(user)
     project.add_developer(user)
     stub_container_registry_config(enabled: true)
     stub_container_registry_tags(repository: :any, tags: [])
@@ -55,7 +55,6 @@ describe "Container Registry" do
   end
 
   def visit_container_registry
-    visit namespace_project_container_registry_index_path(
-      project.namespace, project)
+    visit project_container_registry_index_path(project)
   end
 end
diff --git a/spec/features/copy_as_gfm_spec.rb b/spec/features/copy_as_gfm_spec.rb
index 740f60c05cce4a0ae4d16bf7a01089e6c5eee962..25c5df56d57c79e6f061a738717fe5d32724be2b 100644
--- a/spec/features/copy_as_gfm_spec.rb
+++ b/spec/features/copy_as_gfm_spec.rb
@@ -6,7 +6,7 @@ describe 'Copy as GFM', feature: true, js: true do
   include ActionView::Helpers::JavaScriptHelper
 
   before do
-    login_as :admin
+    gitlab_sign_in :admin
   end
 
   describe 'Copying rendered GFM' do
@@ -16,7 +16,7 @@ describe 'Copy as GFM', feature: true, js: true do
       # `markdown` helper expects a `@project` variable
       @project = @feat.project
 
-      visit namespace_project_issue_path(@project.namespace, @project, @feat.issue)
+      visit project_issue_path(@project, @feat.issue)
     end
 
     # The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert GitLab Flavored Markdown (GFM) to HTML.
@@ -121,13 +121,13 @@ describe 'Copy as GFM', feature: true, js: true do
         # full issue reference
         @feat.issue.to_reference(full: true),
         # issue URL
-        namespace_project_issue_url(@project.namespace, @project, @feat.issue),
+        project_issue_url(@project, @feat.issue),
         # issue URL with note anchor
-        namespace_project_issue_url(@project.namespace, @project, @feat.issue, anchor: 'note_123'),
+        project_issue_url(@project, @feat.issue, anchor: 'note_123'),
         # issue link
-        "[Issue](#{namespace_project_issue_url(@project.namespace, @project, @feat.issue)})",
+        "[Issue](#{project_issue_url(@project, @feat.issue)})",
         # issue link with note anchor
-        "[Issue](#{namespace_project_issue_url(@project.namespace, @project, @feat.issue, anchor: 'note_123')})"
+        "[Issue](#{project_issue_url(@project, @feat.issue, anchor: 'note_123')})"
       )
 
       verify(
@@ -466,7 +466,7 @@ describe 'Copy as GFM', feature: true, js: true do
 
     context 'from a diff' do
       before do
-        visit namespace_project_commit_path(project.namespace, project, sample_commit.id)
+        visit project_commit_path(project, sample_commit.id)
       end
 
       context 'selecting one word of text' do
@@ -507,7 +507,7 @@ describe 'Copy as GFM', feature: true, js: true do
 
     context 'from a blob' do
       before do
-        visit namespace_project_blob_path(project.namespace, project, File.join('master', 'files/ruby/popen.rb'))
+        visit project_blob_path(project, File.join('master', 'files/ruby/popen.rb'))
         wait_for_requests
       end
 
@@ -549,7 +549,7 @@ describe 'Copy as GFM', feature: true, js: true do
 
     context 'from a GFM code block' do
       before do
-        visit namespace_project_blob_path(project.namespace, project, File.join('markdown', 'doc/api/users.md'))
+        visit project_blob_path(project, File.join('markdown', 'doc/api/users.md'))
         wait_for_requests
       end
 
diff --git a/spec/features/cycle_analytics_spec.rb b/spec/features/cycle_analytics_spec.rb
index b416bbd3c7979bccbec1555c5c262e60955eaa02..7825d23c8f929f95a04e813006277bf6fea470a8 100644
--- a/spec/features/cycle_analytics_spec.rb
+++ b/spec/features/cycle_analytics_spec.rb
@@ -14,9 +14,9 @@ feature 'Cycle Analytics', feature: true, js: true do
       before  do
         project.add_master(user)
 
-        login_as(user)
+        gitlab_sign_in(user)
 
-        visit namespace_project_cycle_analytics_path(project.namespace, project)
+        visit project_cycle_analytics_path(project)
         wait_for_requests
       end
 
@@ -38,8 +38,8 @@ feature 'Cycle Analytics', feature: true, js: true do
         create_cycle
         deploy_master
 
-        login_as(user)
-        visit namespace_project_cycle_analytics_path(project.namespace, project)
+        gitlab_sign_in(user)
+        visit project_cycle_analytics_path(project)
       end
 
       it 'shows data on each stage' do
@@ -70,8 +70,8 @@ feature 'Cycle Analytics', feature: true, js: true do
         user.update_attribute(:preferred_language, 'es')
 
         project.team << [user, :master]
-        login_as(user)
-        visit namespace_project_cycle_analytics_path(project.namespace, project)
+        gitlab_sign_in(user)
+        visit project_cycle_analytics_path(project)
         wait_for_requests
       end
 
@@ -93,8 +93,8 @@ feature 'Cycle Analytics', feature: true, js: true do
       create_cycle
       deploy_master
 
-      login_as(guest)
-      visit namespace_project_cycle_analytics_path(project.namespace, project)
+      gitlab_sign_in(guest)
+      visit project_cycle_analytics_path(project)
       wait_for_requests
     end
 
diff --git a/spec/features/dashboard/active_tab_spec.rb b/spec/features/dashboard/active_tab_spec.rb
index ae750be4d4ae26a972356ecec4ee7928cbf3567e..f7ddded10c1a24f3e4355b444eada0b16fce68cb 100644
--- a/spec/features/dashboard/active_tab_spec.rb
+++ b/spec/features/dashboard/active_tab_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 RSpec.describe 'Dashboard Active Tab', js: true, feature: true do
   before do
-    login_as :user
+    gitlab_sign_in :user
   end
 
   shared_examples 'page has active tab' do |title|
diff --git a/spec/features/dashboard/activity_spec.rb b/spec/features/dashboard/activity_spec.rb
index 0764044260e350da067ce456cae127f751b2a7ab..1e9cabe7850bb21e2f36bdcc652dca34ca42c22f 100644
--- a/spec/features/dashboard/activity_spec.rb
+++ b/spec/features/dashboard/activity_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 RSpec.describe 'Dashboard Activity', feature: true do
   before do
-    login_as(create :user)
+    gitlab_sign_in(create :user)
     visit activity_dashboard_path
   end
 
diff --git a/spec/features/dashboard/archived_projects_spec.rb b/spec/features/dashboard/archived_projects_spec.rb
index f33bcbb5318b362be36fb0579e84bb70b6ad27f6..a5ba3e7e3cff6d47be81669852054b9dac0bd47e 100644
--- a/spec/features/dashboard/archived_projects_spec.rb
+++ b/spec/features/dashboard/archived_projects_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe 'Dashboard Archived Project', feature: true do
     project.team << [user, :master]
     archived_project.team << [user, :master]
 
-    login_as(user)
+    gitlab_sign_in(user)
 
     visit dashboard_projects_path
   end
diff --git a/spec/features/dashboard/datetime_on_tooltips_spec.rb b/spec/features/dashboard/datetime_on_tooltips_spec.rb
index 1793e323588fd747b789937c977082d57105c16e..6931d0a840ea59edb3760aa744091bc5554ebe75 100644
--- a/spec/features/dashboard/datetime_on_tooltips_spec.rb
+++ b/spec/features/dashboard/datetime_on_tooltips_spec.rb
@@ -4,7 +4,7 @@ feature 'Tooltips on .timeago dates', feature: true, js: true do
   let(:user)            { create(:user) }
   let(:project)         { create(:project, name: 'test', namespace: user.namespace) }
   let(:created_date)    { Date.yesterday.to_time }
-  let(:expected_format) { created_date.strftime('%b %-d, %Y %l:%M%P') }
+  let(:expected_format) { created_date.in_time_zone.strftime('%b %-d, %Y %l:%M%P') }
 
   context 'on the activity tab' do
     before do
@@ -13,7 +13,7 @@ feature 'Tooltips on .timeago dates', feature: true, js: true do
       Event.create( project: project, author_id: user.id, action: Event::JOINED,
                     updated_at: created_date, created_at: created_date)
 
-      login_as user
+      gitlab_sign_in user
       visit user_path(user)
       wait_for_requests()
 
@@ -30,7 +30,7 @@ feature 'Tooltips on .timeago dates', feature: true, js: true do
       project.team << [user, :master]
       create(:snippet, author: user, updated_at: created_date, created_at: created_date)
 
-      login_as user
+      gitlab_sign_in user
       visit user_snippets_path(user)
       wait_for_requests()
 
diff --git a/spec/features/dashboard/group_spec.rb b/spec/features/dashboard/group_spec.rb
index 8e20fdec8ade63a60f077fd7def3b1203bb1ebce..2f7245950ec42c218bda9bf779e454ac4d19f886 100644
--- a/spec/features/dashboard/group_spec.rb
+++ b/spec/features/dashboard/group_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 RSpec.describe 'Dashboard Group', feature: true do
   before do
-    login_as(:user)
+    gitlab_sign_in(:user)
   end
 
   it 'creates new group', js: true do
diff --git a/spec/features/dashboard/groups_list_spec.rb b/spec/features/dashboard/groups_list_spec.rb
index 7eb254f845120d973ef7e306758d9553ef7c34ee..e520027bc38c6eb11286e9423a7f1e590da0de43 100644
--- a/spec/features/dashboard/groups_list_spec.rb
+++ b/spec/features/dashboard/groups_list_spec.rb
@@ -10,7 +10,7 @@ describe 'Dashboard Groups page', js: true, feature: true do
     group.add_owner(user)
     nested_group.add_owner(user)
 
-    login_as(user)
+    gitlab_sign_in(user)
     visit dashboard_groups_path
 
     expect(page).to have_content(group.full_name)
@@ -23,7 +23,7 @@ describe 'Dashboard Groups page', js: true, feature: true do
       group.add_owner(user)
       nested_group.add_owner(user)
 
-      login_as(user)
+      gitlab_sign_in(user)
 
       visit dashboard_groups_path
     end
@@ -58,7 +58,7 @@ describe 'Dashboard Groups page', js: true, feature: true do
       group.add_owner(user)
       subgroup.add_owner(user)
 
-      login_as(user)
+      gitlab_sign_in(user)
 
       visit dashboard_groups_path
     end
@@ -98,7 +98,7 @@ describe 'Dashboard Groups page', js: true, feature: true do
 
       allow(Kaminari.config).to receive(:default_per_page).and_return(1)
 
-      login_as(user)
+      gitlab_sign_in(user)
       visit dashboard_groups_path
     end
 
diff --git a/spec/features/dashboard/help_spec.rb b/spec/features/dashboard/help_spec.rb
index 2803f7ec62b1a907d909d1d14ab509f9f67aa733..25b0f40c9cda8cccfc32735f884652f758c35d5c 100644
--- a/spec/features/dashboard/help_spec.rb
+++ b/spec/features/dashboard/help_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 RSpec.describe 'Dashboard Help', feature: true do
   before do
-    login_as(:user)
+    gitlab_sign_in(:user)
   end
 
   it 'renders correctly markdown' do
diff --git a/spec/features/dashboard/issuables_counter_spec.rb b/spec/features/dashboard/issuables_counter_spec.rb
index 354267dbee7fe24ef1a49d2bcf2f86c4dfe36bbb..8a8a20fd5b1b884baf1fbf0ec6643e201039ed5a 100644
--- a/spec/features/dashboard/issuables_counter_spec.rb
+++ b/spec/features/dashboard/issuables_counter_spec.rb
@@ -9,7 +9,7 @@ describe 'Navigation bar counter', feature: true, caching: true do
   before do
     issue.assignees = [user]
     merge_request.update(assignee: user)
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   it 'reflects dashboard issues count' do
diff --git a/spec/features/dashboard/issues_spec.rb b/spec/features/dashboard/issues_spec.rb
index 2cea6b1563e302b46cd88c91789f4084386285f8..a57962abbda31b8e0b0ce9e5e22d818726e990af 100644
--- a/spec/features/dashboard/issues_spec.rb
+++ b/spec/features/dashboard/issues_spec.rb
@@ -12,7 +12,7 @@ RSpec.describe 'Dashboard Issues', feature: true do
 
   before do
     [project, project_with_issues_disabled].each { |project| project.team << [current_user, :master] }
-    login_as(current_user)
+    gitlab_sign_in(current_user)
     visit issues_dashboard_path(assignee_id: current_user.id)
   end
 
@@ -59,6 +59,11 @@ RSpec.describe 'Dashboard Issues', feature: true do
       expect(page).to have_content(other_issue.title)
     end
 
+    it 'state filter tabs work' do
+      find('#state-closed').click
+      expect(page).to have_current_path(issues_dashboard_url(assignee_id: current_user.id, scope: 'all', state: 'closed'), url: true)
+    end
+
     it_behaves_like "it has an RSS button with current_user's RSS token"
     it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
   end
diff --git a/spec/features/dashboard/label_filter_spec.rb b/spec/features/dashboard/label_filter_spec.rb
index 4cff12de854a2da9a41f22d7a3717ee4653ea5e6..88bbb9e75b910653d78540384b33daf57b1cb6d2 100644
--- a/spec/features/dashboard/label_filter_spec.rb
+++ b/spec/features/dashboard/label_filter_spec.rb
@@ -11,7 +11,7 @@ describe 'Dashboard > label filter', feature: true, js: true do
     project.labels << label
     project2.labels << label2
 
-    login_as(user)
+    gitlab_sign_in(user)
     visit issues_dashboard_path
   end
 
diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb
index bcb52f602b0a895c9efa8d6092d5e69cbec0ba6e..bb1fb5b3feb7a07b09afde7cbe36058e722941a9 100644
--- a/spec/features/dashboard/merge_requests_spec.rb
+++ b/spec/features/dashboard/merge_requests_spec.rb
@@ -1,18 +1,24 @@
 require 'spec_helper'
 
-describe 'Dashboard Merge Requests' do
+feature 'Dashboard Merge Requests' do
+  include FilterItemSelectHelper
+
   let(:current_user) { create :user }
   let(:project) { create(:empty_project) }
-  let(:project_with_merge_requests_disabled) { create(:empty_project, :merge_requests_disabled) }
 
-  before do
-    [project, project_with_merge_requests_disabled].each { |project| project.team << [current_user, :master] }
+  let(:public_project) { create(:empty_project, :public, :repository) }
+  let(:forked_project) { Projects::ForkService.new(public_project, current_user).execute }
 
-    login_as(current_user)
+  before do
+    project.add_master(current_user)
+    sign_in(current_user)
   end
 
-  describe 'new merge request dropdown' do
+  context 'new merge request dropdown' do
+    let(:project_with_disabled_merge_requests) { create(:empty_project, :merge_requests_disabled) }
+
     before do
+      project_with_disabled_merge_requests.add_master(current_user)
       visit merge_requests_dashboard_path
     end
 
@@ -21,26 +27,87 @@ describe 'Dashboard Merge Requests' do
 
       page.within('.select2-results') do
         expect(page).to have_content(project.name_with_namespace)
-        expect(page).not_to have_content(project_with_merge_requests_disabled.name_with_namespace)
+        expect(page).not_to have_content(project_with_disabled_merge_requests.name_with_namespace)
       end
     end
   end
 
-  it 'should show an empty state' do
-    visit merge_requests_dashboard_path(assignee_id: current_user.id)
+  context 'no merge requests exist' do
+    it 'shows an empty state' do
+      visit merge_requests_dashboard_path(assignee_id: current_user.id)
 
-    expect(page).to have_selector('.empty-state')
+      expect(page).to have_selector('.empty-state')
+    end
   end
 
-  context 'if there are merge requests' do
-    before do
-      create(:merge_request, assignee: current_user, source_project: project)
+  context 'merge requests exist' do
+    let!(:assigned_merge_request) do
+      create(:merge_request, assignee: current_user, target_project: project, source_project: project)
+    end
+
+    let!(:assigned_merge_request_from_fork) do
+      create(:merge_request,
+              source_branch: 'markdown', assignee: current_user,
+              target_project: public_project, source_project: forked_project
+            )
+    end
 
+    let!(:authored_merge_request) do
+      create(:merge_request,
+              source_branch: 'markdown', author: current_user,
+              target_project: project, source_project: project
+            )
+    end
+
+    let!(:authored_merge_request_from_fork) do
+      create(:merge_request,
+              source_branch: 'feature_conflict',
+              author: current_user,
+              target_project: public_project, source_project: forked_project
+            )
+    end
+
+    let!(:other_merge_request) do
+      create(:merge_request,
+              source_branch: 'fix',
+              target_project: project, source_project: project
+            )
+    end
+
+    before do
       visit merge_requests_dashboard_path(assignee_id: current_user.id)
     end
 
-    it 'should not show an empty state' do
-      expect(page).not_to have_selector('.empty-state')
+    it 'shows assigned merge requests' do
+      expect(page).to have_content(assigned_merge_request.title)
+      expect(page).to have_content(assigned_merge_request_from_fork.title)
+
+      expect(page).not_to have_content(authored_merge_request.title)
+      expect(page).not_to have_content(authored_merge_request_from_fork.title)
+      expect(page).not_to have_content(other_merge_request.title)
+    end
+
+    it 'shows authored merge requests', js: true do
+      filter_item_select('Any Assignee', '.js-assignee-search')
+      filter_item_select(current_user.to_reference, '.js-author-search')
+
+      expect(page).to have_content(authored_merge_request.title)
+      expect(page).to have_content(authored_merge_request_from_fork.title)
+
+      expect(page).not_to have_content(assigned_merge_request.title)
+      expect(page).not_to have_content(assigned_merge_request_from_fork.title)
+      expect(page).not_to have_content(other_merge_request.title)
+    end
+
+    it 'shows all merge requests', js: true do
+      filter_item_select('Any Assignee', '.js-assignee-search')
+      filter_item_select('Any Author', '.js-author-search')
+
+      expect(page).to have_content(authored_merge_request.title)
+      expect(page).to have_content(authored_merge_request_from_fork.title)
+      expect(page).to have_content(assigned_merge_request.title)
+      expect(page).to have_content(assigned_merge_request_from_fork.title)
+      expect(page).to have_content(other_merge_request.title)
     end
   end
 end
diff --git a/spec/features/dashboard/milestone_filter_spec.rb b/spec/features/dashboard/milestone_filter_spec.rb
index b5b92c368956afe2f3c1484f3fecce8253112759..b0e4036f27ced55633c2f76a3ef46cd4287ba38c 100644
--- a/spec/features/dashboard/milestone_filter_spec.rb
+++ b/spec/features/dashboard/milestone_filter_spec.rb
@@ -1,15 +1,17 @@
 require 'spec_helper'
 
-describe 'Dashboard > milestone filter', :feature, :js do
+feature 'Dashboard > milestone filter', :feature, :js do
+  include FilterItemSelectHelper
+
   let(:user) { create(:user) }
   let(:project) { create(:project, name: 'test', namespace: user.namespace) }
-  let(:milestone) { create(:milestone, title: "v1.0", project: project) }
-  let(:milestone2) { create(:milestone, title: "v2.0", project: project) }
+  let(:milestone) { create(:milestone, title: 'v1.0', project: project) }
+  let(:milestone2) { create(:milestone, title: 'v2.0', project: project) }
   let!(:issue) { create :issue, author: user, project: project, milestone: milestone }
   let!(:issue2) { create :issue, author: user, project: project, milestone: milestone2 }
 
   before do
-    login_as(user)
+    gitlab_sign_in(user)
     visit issues_dashboard_path(author_id: user.id)
   end
 
@@ -22,17 +24,11 @@ describe 'Dashboard > milestone filter', :feature, :js do
   end
 
   context 'filtering by milestone' do
-    milestone_select = '.js-milestone-select'
+    milestone_select_selector = '.js-milestone-select'
 
     before do
-      find(milestone_select).click
-      wait_for_requests
-
-      page.within('.dropdown-content') do
-        click_link 'v1.0'
-      end
-
-      find(milestone_select).click
+      filter_item_select('v1.0', milestone_select_selector)
+      find(milestone_select_selector).click
       wait_for_requests
     end
 
@@ -49,7 +45,7 @@ describe 'Dashboard > milestone filter', :feature, :js do
 
       expect(find('.milestone-filter')).not_to have_selector('.dropdown.open')
 
-      find(milestone_select).click
+      find(milestone_select_selector).click
 
       expect(find('.dropdown-content')).to have_selector('a.is-active', count: 1)
       expect(find('.dropdown-content a.is-active')).to have_content('v1.0')
diff --git a/spec/features/dashboard/milestone_tabs_spec.rb b/spec/features/dashboard/milestone_tabs_spec.rb
index 0c7b992c5008e5be9caec6f7b7d92be684735e5c..cc4193b180f9e3555d8dff0de1dd5fcaf6d8da1e 100644
--- a/spec/features/dashboard/milestone_tabs_spec.rb
+++ b/spec/features/dashboard/milestone_tabs_spec.rb
@@ -15,7 +15,7 @@ describe 'Dashboard milestone tabs', :js, :feature do
 
   before do
     project.add_master(user)
-    login_as(user)
+    gitlab_sign_in(user)
 
     visit dashboard_milestone_path(milestone.safe_title, title: milestone.title)
   end
@@ -23,7 +23,7 @@ describe 'Dashboard milestone tabs', :js, :feature do
   it 'loads merge requests async' do
     click_link 'Merge Requests'
 
-    expect(page).to have_selector('.merge_requests-sortable-list')
+    expect(page).to have_selector('.milestone-merge_requests-list')
   end
 
   it 'loads participants async' do
diff --git a/spec/features/dashboard/project_member_activity_index_spec.rb b/spec/features/dashboard/project_member_activity_index_spec.rb
index 0ba87d921d0cfe5457dce6252053d738ac541e20..ea0b2e99c3e1699d26de17d088dee092fdc37005 100644
--- a/spec/features/dashboard/project_member_activity_index_spec.rb
+++ b/spec/features/dashboard/project_member_activity_index_spec.rb
@@ -10,7 +10,7 @@ feature 'Project member activity', feature: true, js: true do
 
   def visit_activities_and_wait_with_event(event_type)
     Event.create(project: project, author_id: user.id, action: event_type)
-    visit activity_namespace_project_path(project.namespace, project)
+    visit activity_project_path(project)
     wait_for_requests
   end
 
diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb
index 3568954a54819fddeb11af9e15b95f39494340af..7d1fe2bd43517a02eb0b1adce37e3de27cc6c884 100644
--- a/spec/features/dashboard/projects_spec.rb
+++ b/spec/features/dashboard/projects_spec.rb
@@ -1,13 +1,13 @@
 require 'spec_helper'
 
-RSpec.describe 'Dashboard Projects', feature: true do
+feature 'Dashboard Projects' do
   let(:user) { create(:user) }
-  let(:project) { create(:project, name: "awesome stuff") }
+  let(:project) { create(:project, name: 'awesome stuff') }
   let(:project2) { create(:project, :public, name: 'Community project') }
 
   before do
     project.team << [user, :developer]
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   it 'shows the project the user in a member of in the list' do
@@ -15,13 +15,33 @@ RSpec.describe 'Dashboard Projects', feature: true do
     expect(page).to have_content('awesome stuff')
   end
 
-  it 'shows the last_activity_at attribute as the update date' do
-    now = Time.now
-    project.update_column(:last_activity_at, now)
-
+  it 'shows "New project" button' do
     visit dashboard_projects_path
 
-    expect(page).to have_xpath("//time[@datetime='#{now.getutc.iso8601}']")
+    page.within '#content-body' do
+      expect(page).to have_link('New project')
+    end
+  end
+
+  context 'when last_repository_updated_at, last_activity_at and update_at are present' do
+    it 'shows the last_repository_updated_at attribute as the update date' do
+      project.update_attributes!(last_repository_updated_at: Time.now, last_activity_at: 1.hour.ago)
+
+      visit dashboard_projects_path
+
+      expect(page).to have_xpath("//time[@datetime='#{project.last_repository_updated_at.getutc.iso8601}']")
+    end
+  end
+
+  context 'when last_repository_updated_at and last_activity_at are missing' do
+    it 'shows the updated_at attribute as the update date' do
+      project.update_attributes!(last_repository_updated_at: nil, last_activity_at: nil)
+      project.touch
+
+      visit dashboard_projects_path
+
+      expect(page).to have_xpath("//time[@datetime='#{project.updated_at.getutc.iso8601}']")
+    end
   end
 
   context 'when on Starred projects tab' do
@@ -35,8 +55,8 @@ RSpec.describe 'Dashboard Projects', feature: true do
     end
   end
 
-  describe "with a pipeline", redis: true do
-    let!(:pipeline) {  create(:ci_pipeline, project: project, sha: project.commit.sha) }
+  describe 'with a pipeline', redis: true do
+    let(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.sha) }
 
     before do
       # Since the cache isn't updated when a new pipeline is created
@@ -48,7 +68,7 @@ RSpec.describe 'Dashboard Projects', feature: true do
     it 'shows that the last pipeline passed' do
       visit dashboard_projects_path
 
-      expect(page).to have_xpath("//a[@href='#{pipelines_namespace_project_commit_path(project.namespace, project, project.commit)}']")
+      expect(page).to have_xpath("//a[@href='#{pipelines_project_commit_path(project, project.commit)}']")
     end
   end
 
diff --git a/spec/features/dashboard/shortcuts_spec.rb b/spec/features/dashboard/shortcuts_spec.rb
index 349b948eaeec26e835d917d7485a524c255cff71..525b0e1b2107e715dbeee09af02887e32281a612 100644
--- a/spec/features/dashboard/shortcuts_spec.rb
+++ b/spec/features/dashboard/shortcuts_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 feature 'Dashboard shortcuts', :feature, :js do
   context 'logged in' do
     before do
-      login_as :user
+      gitlab_sign_in :user
       visit root_dashboard_path
     end
 
diff --git a/spec/features/dashboard/snippets_spec.rb b/spec/features/dashboard/snippets_spec.rb
index c6ba118220aef6ac9029bb229dc91e71a50ff96b..0c069ae5cf047359a0423548088b49db47138741 100644
--- a/spec/features/dashboard/snippets_spec.rb
+++ b/spec/features/dashboard/snippets_spec.rb
@@ -6,7 +6,7 @@ describe 'Dashboard snippets', feature: true do
     let!(:snippets) { create_list(:project_snippet, 2, :public, author: project.owner, project: project) }
     before do
       allow(Snippet).to receive(:default_per_page).and_return(1)
-      login_as(project.owner)
+      gitlab_sign_in(project.owner)
       visit dashboard_snippets_path
     end
 
@@ -25,7 +25,7 @@ describe 'Dashboard snippets', feature: true do
     end
 
     before do
-      login_as(user)
+      gitlab_sign_in(user)
 
       visit dashboard_snippets_path
     end
diff --git a/spec/features/todos/target_state_spec.rb b/spec/features/dashboard/todos/target_state_spec.rb
similarity index 82%
rename from spec/features/todos/target_state_spec.rb
rename to spec/features/dashboard/todos/target_state_spec.rb
index 32fa88a2b21cbdae47a004d0889439ed29c0b66c..030a86d1c01b352b18497ad0d877feb6cb6d7842 100644
--- a/spec/features/todos/target_state_spec.rb
+++ b/spec/features/dashboard/todos/target_state_spec.rb
@@ -1,12 +1,12 @@
 require 'rails_helper'
 
-feature 'Todo target states', feature: true do
+feature 'Dashboard > Todo target states' do
   let(:user)    { create(:user) }
   let(:author)  { create(:user) }
-  let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
+  let(:project) { create(:project, :public) }
 
   before do
-    login_as user
+    sign_in(user)
   end
 
   scenario 'on a closed issue todo has closed label' do
@@ -30,7 +30,7 @@ feature 'Todo target states', feature: true do
   end
 
   scenario 'on a merged merge request todo has merged label' do
-    mr_merged = create(:merge_request, :simple, author: user, state: 'merged')
+    mr_merged = create(:merge_request, :simple, :merged, author: user)
     create_todo mr_merged
     visit dashboard_todos_path
 
@@ -40,7 +40,7 @@ feature 'Todo target states', feature: true do
   end
 
   scenario 'on a closed merge request todo has closed label' do
-    mr_closed = create(:merge_request, :simple, author: user, state: 'closed')
+    mr_closed = create(:merge_request, :simple, :closed, author: user)
     create_todo mr_closed
     visit dashboard_todos_path
 
diff --git a/spec/features/todos/todos_filtering_spec.rb b/spec/features/dashboard/todos/todos_filtering_spec.rb
similarity index 92%
rename from spec/features/todos/todos_filtering_spec.rb
rename to spec/features/dashboard/todos/todos_filtering_spec.rb
index bbfa4e08379574bfedf2c66f7d60c88f5c2391e7..0a363259fe775659395aa6cf17656ea6e9f9f69a 100644
--- a/spec/features/todos/todos_filtering_spec.rb
+++ b/spec/features/dashboard/todos/todos_filtering_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe 'Dashboard > User filters todos', feature: true, js: true do
+feature 'Dashboard > User filters todos', js: true do
   let(:user_1)    { create(:user, username: 'user_1', name: 'user_1') }
   let(:user_2)    { create(:user, username: 'user_2', name: 'user_2') }
 
@@ -17,7 +17,7 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
 
     project_1.team << [user_1, :developer]
     project_2.team << [user_1, :developer]
-    login_as(user_1)
+    sign_in(user_1)
     visit dashboard_todos_path
   end
 
@@ -34,7 +34,7 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
     expect(page).not_to have_content project_2.name_with_namespace
   end
 
-  context "Author filter" do
+  context 'Author filter' do
     it 'filters by author' do
       click_button 'Author'
 
@@ -49,18 +49,18 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
       expect(find('.todos-list')).not_to have_content 'issue'
     end
 
-    it "shows only authors of existing todos" do
+    it 'shows only authors of existing todos' do
       click_button 'Author'
 
       within '.dropdown-menu-author' do
-        # It should contain two users + "Any Author"
+        # It should contain two users + 'Any Author'
         expect(page).to have_selector('.dropdown-menu-user-link', count: 3)
         expect(page).to have_content(user_1.name)
         expect(page).to have_content(user_2.name)
       end
     end
 
-    it "shows only authors of existing done todos" do
+    it 'shows only authors of existing done todos' do
       user_3 = create :user
       user_4 = create :user
       create(:todo, user: user_1, author: user_3, project: project_1, target: issue, action: 1, state: :done)
@@ -74,7 +74,7 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
       click_button 'Author'
 
       within '.dropdown-menu-author' do
-        # It should contain two users + "Any Author"
+        # It should contain two users + 'Any Author'
         expect(page).to have_selector('.dropdown-menu-user-link', count: 3)
         expect(page).to have_content(user_3.name)
         expect(page).to have_content(user_4.name)
diff --git a/spec/features/todos/todos_sorting_spec.rb b/spec/features/dashboard/todos/todos_sorting_spec.rb
similarity index 60%
rename from spec/features/todos/todos_sorting_spec.rb
rename to spec/features/dashboard/todos/todos_sorting_spec.rb
index f012d2508873be1b7201b9dc861bdfb24bbd3ad0..5858f4aa101bf498db5909dd34e10aca4e435128 100644
--- a/spec/features/todos/todos_sorting_spec.rb
+++ b/spec/features/dashboard/todos/todos_sorting_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe "Dashboard > User sorts todos", feature: true do
+feature 'Dashboard > User sorts todos' do
   let(:user)    { create(:user) }
   let(:project) { create(:empty_project) }
 
@@ -18,7 +18,7 @@ describe "Dashboard > User sorts todos", feature: true do
     let(:issue_3) { create(:issue, title: 'issue_3', project: project) }
     let(:issue_4) { create(:issue, title: 'issue_4', project: project) }
 
-    let!(:merge_request_1) { create(:merge_request, source_project: project, title: "merge_request_1") }
+    let!(:merge_request_1) { create(:merge_request, source_project: project, title: 'merge_request_1') }
 
     before do
       create(:todo, user: user, project: project, target: issue_4, created_at: 5.hours.ago)
@@ -32,41 +32,41 @@ describe "Dashboard > User sorts todos", feature: true do
       issue_2.labels         << label_3
       issue_1.labels         << label_2
 
-      login_as(user)
+      sign_in(user)
       visit dashboard_todos_path
     end
 
-    it "sorts with oldest created todos first" do
-      click_link "Last created"
+    it 'sorts with oldest created todos first' do
+      click_link 'Last created'
 
       results_list = page.find('.todos-list')
-      expect(results_list.all('p')[0]).to have_content("merge_request_1")
-      expect(results_list.all('p')[1]).to have_content("issue_1")
-      expect(results_list.all('p')[2]).to have_content("issue_3")
-      expect(results_list.all('p')[3]).to have_content("issue_2")
-      expect(results_list.all('p')[4]).to have_content("issue_4")
+      expect(results_list.all('p')[0]).to have_content('merge_request_1')
+      expect(results_list.all('p')[1]).to have_content('issue_1')
+      expect(results_list.all('p')[2]).to have_content('issue_3')
+      expect(results_list.all('p')[3]).to have_content('issue_2')
+      expect(results_list.all('p')[4]).to have_content('issue_4')
     end
 
-    it "sorts with newest created todos first" do
-      click_link "Oldest created"
+    it 'sorts with newest created todos first' do
+      click_link 'Oldest created'
 
       results_list = page.find('.todos-list')
-      expect(results_list.all('p')[0]).to have_content("issue_4")
-      expect(results_list.all('p')[1]).to have_content("issue_2")
-      expect(results_list.all('p')[2]).to have_content("issue_3")
-      expect(results_list.all('p')[3]).to have_content("issue_1")
-      expect(results_list.all('p')[4]).to have_content("merge_request_1")
+      expect(results_list.all('p')[0]).to have_content('issue_4')
+      expect(results_list.all('p')[1]).to have_content('issue_2')
+      expect(results_list.all('p')[2]).to have_content('issue_3')
+      expect(results_list.all('p')[3]).to have_content('issue_1')
+      expect(results_list.all('p')[4]).to have_content('merge_request_1')
     end
 
-    it "sorts by label priority" do
-      click_link "Label priority"
+    it 'sorts by label priority' do
+      click_link 'Label priority'
 
       results_list = page.find('.todos-list')
-      expect(results_list.all('p')[0]).to have_content("issue_3")
-      expect(results_list.all('p')[1]).to have_content("merge_request_1")
-      expect(results_list.all('p')[2]).to have_content("issue_1")
-      expect(results_list.all('p')[3]).to have_content("issue_2")
-      expect(results_list.all('p')[4]).to have_content("issue_4")
+      expect(results_list.all('p')[0]).to have_content('issue_3')
+      expect(results_list.all('p')[1]).to have_content('merge_request_1')
+      expect(results_list.all('p')[2]).to have_content('issue_1')
+      expect(results_list.all('p')[3]).to have_content('issue_2')
+      expect(results_list.all('p')[4]).to have_content('issue_4')
     end
   end
 
@@ -83,17 +83,17 @@ describe "Dashboard > User sorts todos", feature: true do
       create(:todo, user: user, project: project, target: issue_2)
       create(:todo, user: user, project: project, target: merge_request_1)
 
-      login_as(user)
+      gitlab_sign_in(user)
       visit dashboard_todos_path
     end
 
     it "doesn't mix issues and merge requests label priorities" do
-      click_link "Label priority"
+      click_link 'Label priority'
 
       results_list = page.find('.todos-list')
-      expect(results_list.all('p')[0]).to have_content("issue_1")
-      expect(results_list.all('p')[1]).to have_content("issue_2")
-      expect(results_list.all('p')[2]).to have_content("merge_request_1")
+      expect(results_list.all('p')[0]).to have_content('issue_1')
+      expect(results_list.all('p')[1]).to have_content('issue_2')
+      expect(results_list.all('p')[2]).to have_content('merge_request_1')
     end
   end
 end
diff --git a/spec/features/dashboard/todos/todos_spec.rb b/spec/features/dashboard/todos/todos_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..30bab7eeaa777f8bdc61d51597543ef2b04fa267
--- /dev/null
+++ b/spec/features/dashboard/todos/todos_spec.rb
@@ -0,0 +1,338 @@
+require 'spec_helper'
+
+feature 'Dashboard Todos' do
+  let(:user)    { create(:user) }
+  let(:author)  { create(:user) }
+  let(:project) { create(:project, :public) }
+  let(:issue)   { create(:issue, due_date: Date.today) }
+
+  context 'User does not have todos' do
+    before do
+      sign_in(user)
+      visit dashboard_todos_path
+    end
+
+    it 'shows "All done" message' do
+      expect(page).to have_content 'Todos let you see what you should do next.'
+    end
+  end
+
+  context 'User has a todo', js: true do
+    before do
+      create(:todo, :mentioned, user: user, project: project, target: issue, author: author)
+      sign_in(user)
+
+      visit dashboard_todos_path
+    end
+
+    it 'has todo present' do
+      expect(page).to have_selector('.todos-list .todo', count: 1)
+    end
+
+    it 'shows due date as today' do
+      within first('.todo') do
+        expect(page).to have_content 'Due today'
+      end
+    end
+
+    shared_examples 'deleting the todo' do
+      before do
+        within first('.todo') do
+          click_link 'Done'
+        end
+      end
+
+      it 'is marked as done-reversible in the list' do
+        expect(page).to have_selector('.todos-list .todo.todo-pending.done-reversible')
+      end
+
+      it 'shows Undo button' do
+        expect(page).to have_selector('.js-undo-todo', visible: true)
+        expect(page).to have_selector('.js-done-todo', visible: false)
+      end
+
+      it 'updates todo count' do
+        expect(page).to have_content 'To do 0'
+        expect(page).to have_content 'Done 1'
+      end
+
+      it 'has not "All done" message' do
+        expect(page).not_to have_selector('.todos-all-done')
+      end
+    end
+
+    shared_examples 'deleting and restoring the todo' do
+      before do
+        within first('.todo') do
+          click_link 'Done'
+          wait_for_requests
+          click_link 'Undo'
+        end
+      end
+
+      it 'is marked back as pending in the list' do
+        expect(page).not_to have_selector('.todos-list .todo.todo-pending.done-reversible')
+        expect(page).to have_selector('.todos-list .todo.todo-pending')
+      end
+
+      it 'shows Done button' do
+        expect(page).to have_selector('.js-undo-todo', visible: false)
+        expect(page).to have_selector('.js-done-todo', visible: true)
+      end
+
+      it 'updates todo count' do
+        expect(page).to have_content 'To do 1'
+        expect(page).to have_content 'Done 0'
+      end
+    end
+
+    it_behaves_like 'deleting the todo'
+    it_behaves_like 'deleting and restoring the todo'
+
+    context 'todo is stale on the page' do
+      before do
+        todos = TodosFinder.new(user, state: :pending).execute
+        TodoService.new.mark_todos_as_done(todos, user)
+      end
+
+      it_behaves_like 'deleting the todo'
+      it_behaves_like 'deleting and restoring the todo'
+    end
+  end
+
+  context 'User created todos for themself' do
+    before do
+      sign_in(user)
+    end
+
+    context 'issue assigned todo' do
+      before do
+        create(:todo, :assigned, user: user, project: project, target: issue, author: user)
+        visit dashboard_todos_path
+      end
+
+      it 'shows issue assigned to yourself message' do
+        page.within('.js-todos-all')  do
+          expect(page).to have_content("You assigned issue #{issue.to_reference(full: true)} to yourself")
+        end
+      end
+    end
+
+    context 'marked todo' do
+      before do
+        create(:todo, :marked, user: user, project: project, target: issue, author: user)
+        visit dashboard_todos_path
+      end
+
+      it 'shows you added a todo message' do
+        page.within('.js-todos-all')  do
+          expect(page).to have_content("You added a todo for issue #{issue.to_reference(full: true)}")
+          expect(page).not_to have_content('to yourself')
+        end
+      end
+    end
+
+    context 'mentioned todo' do
+      before do
+        create(:todo, :mentioned, user: user, project: project, target: issue, author: user)
+        visit dashboard_todos_path
+      end
+
+      it 'shows you mentioned yourself message' do
+        page.within('.js-todos-all')  do
+          expect(page).to have_content("You mentioned yourself on issue #{issue.to_reference(full: true)}")
+          expect(page).not_to have_content('to yourself')
+        end
+      end
+    end
+
+    context 'directly_addressed todo' do
+      before do
+        create(:todo, :directly_addressed, user: user, project: project, target: issue, author: user)
+        visit dashboard_todos_path
+      end
+
+      it 'shows you directly addressed yourself message' do
+        page.within('.js-todos-all')  do
+          expect(page).to have_content("You directly addressed yourself on issue #{issue.to_reference(full: true)}")
+          expect(page).not_to have_content('to yourself')
+        end
+      end
+    end
+
+    context 'approval todo' do
+      let(:merge_request) { create(:merge_request) }
+
+      before do
+        create(:todo, :approval_required, user: user, project: project, target: merge_request, author: user)
+        visit dashboard_todos_path
+      end
+
+      it 'shows you set yourself as an approver message' do
+        page.within('.js-todos-all')  do
+          expect(page).to have_content("You set yourself as an approver for merge request #{merge_request.to_reference(full: true)}")
+          expect(page).not_to have_content('to yourself')
+        end
+      end
+    end
+  end
+
+  context 'User has done todos', js: true do
+    before do
+      create(:todo, :mentioned, :done, user: user, project: project, target: issue, author: author)
+      sign_in(user)
+      visit dashboard_todos_path(state: :done)
+    end
+
+    it 'has the done todo present' do
+      expect(page).to have_selector('.todos-list .todo.todo-done', count: 1)
+    end
+
+    describe 'restoring the todo' do
+      before do
+        within first('.todo') do
+          click_link 'Add todo'
+        end
+      end
+
+      it 'is removed from the list' do
+        expect(page).not_to have_selector('.todos-list .todo.todo-done')
+      end
+
+      it 'updates todo count' do
+        expect(page).to have_content 'To do 1'
+        expect(page).to have_content 'Done 0'
+      end
+    end
+  end
+
+  context 'User has Todos with labels spanning multiple projects' do
+    before do
+      label1 = create(:label, project: project)
+      note1 = create(:note_on_issue, note: "Hello #{label1.to_reference(format: :name)}", noteable_id: issue.id, noteable_type: 'Issue', project: issue.project)
+      create(:todo, :mentioned, project: project, target: issue, user: user, note_id: note1.id)
+
+      project2 = create(:project, :public)
+      label2 = create(:label, project: project2)
+      issue2 = create(:issue, project: project2)
+      note2 = create(:note_on_issue, note: "Test #{label2.to_reference(format: :name)}", noteable_id: issue2.id, noteable_type: 'Issue', project: project2)
+      create(:todo, :mentioned, project: project2, target: issue2, user: user, note_id: note2.id)
+
+      gitlab_sign_in(user)
+      visit dashboard_todos_path
+    end
+
+    it 'shows page with two Todos' do
+      expect(page).to have_selector('.todos-list .todo', count: 2)
+    end
+  end
+
+  context 'User has multiple pages of Todos' do
+    before do
+      allow(Todo).to receive(:default_per_page).and_return(1)
+
+      # Create just enough records to cause us to paginate
+      create_list(:todo, 2, :mentioned, user: user, project: project, target: issue, author: author)
+
+      sign_in(user)
+    end
+
+    it 'is paginated' do
+      visit dashboard_todos_path
+
+      expect(page).to have_selector('.gl-pagination')
+    end
+
+    it 'is has the right number of pages' do
+      visit dashboard_todos_path
+
+      expect(page).to have_selector('.gl-pagination .page', count: 2)
+    end
+
+    describe 'mark all as done', js: true do
+      before do
+        visit dashboard_todos_path
+        find('.js-todos-mark-all').trigger('click')
+      end
+
+      it 'shows "All done" message!' do
+        expect(page).to have_content 'To do 0'
+        expect(page).to have_content "You're all done!"
+        expect(page).not_to have_selector('.gl-pagination')
+      end
+
+      it 'shows "Undo mark all as done" button' do
+        expect(page).to have_selector('.js-todos-mark-all', visible: false)
+        expect(page).to have_selector('.js-todos-undo-all', visible: true)
+      end
+    end
+
+    describe 'undo mark all as done', js: true do
+      before do
+        visit dashboard_todos_path
+      end
+
+      it 'shows the restored todo list' do
+        mark_all_and_undo
+
+        expect(page).to have_selector('.todos-list .todo', count: 1)
+        expect(page).to have_selector('.gl-pagination')
+        expect(page).not_to have_content "You're all done!"
+      end
+
+      it 'updates todo count' do
+        mark_all_and_undo
+
+        expect(page).to have_content 'To do 2'
+        expect(page).to have_content 'Done 0'
+      end
+
+      it 'shows "Mark all as done" button' do
+        mark_all_and_undo
+
+        expect(page).to have_selector('.js-todos-mark-all', visible: true)
+        expect(page).to have_selector('.js-todos-undo-all', visible: false)
+      end
+
+      context 'User has deleted a todo' do
+        before do
+          within first('.todo') do
+            click_link 'Done'
+          end
+        end
+
+        it 'shows the restored todo list with the deleted todo' do
+          mark_all_and_undo
+
+          expect(page).to have_selector('.todos-list .todo.todo-pending', count: 1)
+        end
+      end
+
+      def mark_all_and_undo
+        find('.js-todos-mark-all').trigger('click')
+        wait_for_requests
+        find('.js-todos-undo-all').trigger('click')
+        wait_for_requests
+      end
+    end
+  end
+
+  context 'User has a Build Failed todo' do
+    let!(:todo) { create(:todo, :build_failed, user: user, project: project, author: author) }
+
+    before do
+      sign_in(user)
+      visit dashboard_todos_path
+    end
+
+    it 'shows the todo' do
+      expect(page).to have_content 'The build failed for merge request'
+    end
+
+    it 'links to the pipelines for the merge request' do
+      href = pipelines_project_merge_request_path(project, todo.target)
+
+      expect(page).to have_link "merge request #{todo.target.to_reference(full: true)}", href
+    end
+  end
+end
diff --git a/spec/features/dashboard/user_filters_projects_spec.rb b/spec/features/dashboard/user_filters_projects_spec.rb
index 34d6257f5fd77b217b016a75700e2e63fd108f99..e9f34760143b5af473cb807668bcca7cc4c2d966 100644
--- a/spec/features/dashboard/user_filters_projects_spec.rb
+++ b/spec/features/dashboard/user_filters_projects_spec.rb
@@ -9,7 +9,7 @@ describe 'Dashboard > User filters projects', :feature do
   before do
     project.team << [user, :master]
 
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   describe 'filtering personal projects' do
diff --git a/spec/features/dashboard_issues_spec.rb b/spec/features/dashboard_issues_spec.rb
index 1c53f6dff06561b6ce72db0e99ea494e68b72b6b..c4dbaad2895836967e4da23ced815d4b791043c4 100644
--- a/spec/features/dashboard_issues_spec.rb
+++ b/spec/features/dashboard_issues_spec.rb
@@ -8,7 +8,7 @@ describe "Dashboard Issues filtering", feature: true, js: true do
   context 'filtering by milestone' do
     before do
       project.team << [user, :master]
-      login_as(user)
+      gitlab_sign_in(user)
 
       create(:issue, project: project, author: user, assignees: [user])
       create(:issue, project: project, author: user, assignees: [user], milestone: milestone)
diff --git a/spec/features/dashboard_milestones_spec.rb b/spec/features/dashboard_milestones_spec.rb
index f32fddbc9fadafc0e7ec8aef58a7d343fc7acc1a..b308a2297b949744eb05f33a2b664fcc1727f3b9 100644
--- a/spec/features/dashboard_milestones_spec.rb
+++ b/spec/features/dashboard_milestones_spec.rb
@@ -17,7 +17,7 @@ feature 'Dashboard > Milestones', feature: true do
     let!(:milestone) { create(:milestone, project: project) }
     before do
       project.team << [user, :master]
-      login_with(user)
+      gitlab_sign_in(user)
       visit dashboard_milestones_path
     end
 
diff --git a/spec/features/discussion_comments/commit_spec.rb b/spec/features/discussion_comments/commit_spec.rb
index 96e0b78f6b9446420083a5cdf2348c526cdbf34d..620184e29330bc5d54813b50de86c0ab3ddabf92 100644
--- a/spec/features/discussion_comments/commit_spec.rb
+++ b/spec/features/discussion_comments/commit_spec.rb
@@ -9,9 +9,9 @@ describe 'Discussion Comments Merge Request', :feature, :js do
 
   before do
     project.add_master(user)
-    login_as(user)
+    gitlab_sign_in(user)
 
-    visit namespace_project_commit_path(project.namespace, project, sample_commit.id)
+    visit project_commit_path(project, sample_commit.id)
   end
 
   it_behaves_like 'discussion comments', 'commit'
diff --git a/spec/features/discussion_comments/issue_spec.rb b/spec/features/discussion_comments/issue_spec.rb
index ccc9efccd1855fa12f6f80ea1a742c52a4341a0b..f90f82f8a4813056be8cc088bc80ab1737b1cd1d 100644
--- a/spec/features/discussion_comments/issue_spec.rb
+++ b/spec/features/discussion_comments/issue_spec.rb
@@ -7,9 +7,9 @@ describe 'Discussion Comments Issue', :feature, :js do
 
   before do
     project.add_master(user)
-    login_as(user)
+    gitlab_sign_in(user)
 
-    visit namespace_project_issue_path(project.namespace, project, issue)
+    visit project_issue_path(project, issue)
   end
 
   it_behaves_like 'discussion comments', 'issue'
diff --git a/spec/features/discussion_comments/merge_request_spec.rb b/spec/features/discussion_comments/merge_request_spec.rb
index f99ebeb9cd927ad0bca7f2050a960028483f1f1b..577d9c69bbc0a8a0ca740f836f2da7c413e985cc 100644
--- a/spec/features/discussion_comments/merge_request_spec.rb
+++ b/spec/features/discussion_comments/merge_request_spec.rb
@@ -7,9 +7,9 @@ describe 'Discussion Comments Merge Request', :feature, :js do
 
   before do
     project.add_master(user)
-    login_as(user)
+    gitlab_sign_in(user)
 
-    visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+    visit project_merge_request_path(project, merge_request)
   end
 
   it_behaves_like 'discussion comments', 'merge request'
diff --git a/spec/features/discussion_comments/snippets_spec.rb b/spec/features/discussion_comments/snippets_spec.rb
index 19a306511b2efe901afc642f2c9ae916a523cd26..a59be88db7db3b4872db7ac3f1fa3e2cbb0d3870 100644
--- a/spec/features/discussion_comments/snippets_spec.rb
+++ b/spec/features/discussion_comments/snippets_spec.rb
@@ -7,9 +7,9 @@ describe 'Discussion Comments Issue', :feature, :js do
 
   before do
     project.add_master(user)
-    login_as(user)
+    gitlab_sign_in(user)
 
-    visit namespace_project_snippet_path(project.namespace, project, snippet)
+    visit project_snippet_path(project, snippet)
   end
 
   it_behaves_like 'discussion comments', 'snippet'
diff --git a/spec/features/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb
index 36b0c371e6eaf37ad2434c0e36a3063e7c831871..f45752ab3f33de8dc3eac3971e40da342e64f683 100644
--- a/spec/features/expand_collapse_diffs_spec.rb
+++ b/spec/features/expand_collapse_diffs_spec.rb
@@ -10,11 +10,11 @@ feature 'Expand and collapse diffs', js: true, feature: true do
     allow(Gitlab::Git::Diff).to receive(:size_limit).and_return(100.kilobytes)
     allow(Gitlab::Git::Diff).to receive(:collapse_limit).and_return(10.kilobytes)
 
-    login_as :admin
+    gitlab_sign_in :admin
 
     # Ensure that undiffable.md is in .gitattributes
     project.repository.copy_gitattributes(branch)
-    visit namespace_project_commit_path(project.namespace, project, project.commit(branch))
+    visit project_commit_path(project, project.commit(branch))
     execute_script('window.ajaxUris = []; $(document).ajaxSend(function(event, xhr, settings) { ajaxUris.push(settings.url) });')
   end
 
@@ -38,7 +38,7 @@ feature 'Expand and collapse diffs', js: true, feature: true do
     expect(large_diff).not_to have_selector('.code')
     expect(large_diff).to have_selector('.nothing-here-block')
 
-    visit namespace_project_commit_path(project.namespace, project, project.commit(branch), anchor: "#{large_diff[:id]}_0_1")
+    visit project_commit_path(project, project.commit(branch), anchor: "#{large_diff[:id]}_0_1")
     execute_script('window.location.reload()')
 
     wait_for_requests
@@ -52,7 +52,7 @@ feature 'Expand and collapse diffs', js: true, feature: true do
     expect(large_diff).not_to have_selector('.code')
     expect(large_diff).to have_selector('.nothing-here-block')
 
-    visit namespace_project_commit_path(project.namespace, project, project.commit(branch), anchor: large_diff[:id])
+    visit project_commit_path(project, project.commit(branch), anchor: large_diff[:id])
     execute_script('window.location.reload()')
 
     wait_for_requests
@@ -129,7 +129,7 @@ feature 'Expand and collapse diffs', js: true, feature: true do
 
         before do
           large_diff.find('.diff-line-num', match: :prefer_exact).hover
-          large_diff.find('.add-diff-note').click
+          large_diff.find('.add-diff-note', match: :prefer_exact).click
           large_diff.find('.note-textarea').send_keys comment_text
           large_diff.find_button('Comment').click
           wait_for_requests
diff --git a/spec/features/explore/groups_list_spec.rb b/spec/features/explore/groups_list_spec.rb
index d4284ed099b390a76bfe07d43bd4ac97550e444c..6be5dee0c3c52ea6e55d8c8e9fabcd29e5a37875 100644
--- a/spec/features/explore/groups_list_spec.rb
+++ b/spec/features/explore/groups_list_spec.rb
@@ -10,7 +10,7 @@ describe 'Explore Groups page', :js, :feature do
   before do
     group.add_owner(user)
 
-    login_as(user)
+    gitlab_sign_in(user)
 
     visit explore_groups_path
   end
diff --git a/spec/features/explore/new_menu_spec.rb b/spec/features/explore/new_menu_spec.rb
index 15a6354211b03bf00ff688c06ef6b4763e9fc8be..5cd72e1d24956dda6ea9f8c6009c8e5ca28a44fa 100644
--- a/spec/features/explore/new_menu_spec.rb
+++ b/spec/features/explore/new_menu_spec.rb
@@ -16,7 +16,7 @@ feature 'Top Plus Menu', feature: true, js: true do
 
   context 'used by full user' do
     before do
-      login_as(user)
+      gitlab_sign_in(user)
     end
 
     scenario 'click on New project shows new project page' do
@@ -47,7 +47,7 @@ feature 'Top Plus Menu', feature: true, js: true do
     end
 
     scenario 'click on New issue shows new issue page' do
-      visit namespace_project_path(project.namespace, project)
+      visit project_path(project)
 
       click_topmenuitem("New issue")
 
@@ -56,7 +56,7 @@ feature 'Top Plus Menu', feature: true, js: true do
     end
 
     scenario 'click on New merge request shows new merge request page' do
-      visit namespace_project_path(project.namespace, project)
+      visit project_path(project)
 
       click_topmenuitem("New merge request")
 
@@ -66,7 +66,7 @@ feature 'Top Plus Menu', feature: true, js: true do
     end
 
     scenario 'click on New project snippet shows new snippet page' do
-      visit namespace_project_path(project.namespace, project)
+      visit project_path(project)
 
       page.within '.header-content' do
         find('.header-new-dropdown-toggle').trigger('click')
@@ -103,11 +103,11 @@ feature 'Top Plus Menu', feature: true, js: true do
 
   context 'used by guest user' do
     before do
-      login_as(guest_user)
+      gitlab_sign_in(guest_user)
     end
 
     scenario 'click on New issue shows new issue page' do
-      visit namespace_project_path(project.namespace, project)
+      visit project_path(project)
 
       click_topmenuitem("New issue")
 
@@ -116,31 +116,31 @@ feature 'Top Plus Menu', feature: true, js: true do
     end
 
     scenario 'has no New merge request menu item' do
-      visit namespace_project_path(project.namespace, project)
+      visit project_path(project)
 
       hasnot_topmenuitem("New merge request")
     end
 
     scenario 'has no New project snippet menu item' do
-      visit namespace_project_path(project.namespace, project)
+      visit project_path(project)
 
       expect(find('.header-new.dropdown')).not_to have_selector('.header-new-project-snippet')
     end
 
     scenario 'public project has no New Issue Button' do
-      visit namespace_project_path(public_project.namespace, public_project)
+      visit project_path(public_project)
 
       hasnot_topmenuitem("New issue")
     end
 
     scenario 'public project has no New merge request menu item' do
-      visit namespace_project_path(public_project.namespace, public_project)
+      visit project_path(public_project)
 
       hasnot_topmenuitem("New merge request")
     end
 
     scenario 'public project has no New project snippet menu item' do
-      visit namespace_project_path(public_project.namespace, public_project)
+      visit project_path(public_project)
 
       expect(find('.header-new.dropdown')).not_to have_selector('.header-new-project-snippet')
     end
diff --git a/spec/features/gitlab_flavored_markdown_spec.rb b/spec/features/gitlab_flavored_markdown_spec.rb
index 550924123400eb3b854f53077e37727c0595c75a..8659a8686829a1cbb917af64ddd420803c2f7ecf 100644
--- a/spec/features/gitlab_flavored_markdown_spec.rb
+++ b/spec/features/gitlab_flavored_markdown_spec.rb
@@ -1,6 +1,7 @@
 require 'spec_helper'
 
 describe "GitLab Flavored Markdown", feature: true do
+  let(:user) { create(:user) }
   let(:project) { create(:empty_project) }
   let(:issue) { create(:issue, project: project) }
   let(:fred) do
@@ -10,8 +11,8 @@ describe "GitLab Flavored Markdown", feature: true do
   end
 
   before do
-    login_as(:user)
-    project.add_developer(@user)
+    sign_in(user)
+    project.add_developer(user)
   end
 
   describe "for commits" do
@@ -19,30 +20,30 @@ describe "GitLab Flavored Markdown", feature: true do
     let(:commit) { project.commit }
 
     before do
-      allow_any_instance_of(Commit).to receive(:title).
-        and_return("fix #{issue.to_reference}\n\nask #{fred.to_reference} for details")
+      allow_any_instance_of(Commit).to receive(:title)
+        .and_return("fix #{issue.to_reference}\n\nask #{fred.to_reference} for details")
     end
 
     it "renders title in commits#index" do
-      visit namespace_project_commits_path(project.namespace, project, 'master', limit: 1)
+      visit project_commits_path(project, 'master', limit: 1)
 
       expect(page).to have_link(issue.to_reference)
     end
 
     it "renders title in commits#show" do
-      visit namespace_project_commit_path(project.namespace, project, commit)
+      visit project_commit_path(project, commit)
 
       expect(page).to have_link(issue.to_reference)
     end
 
     it "renders description in commits#show" do
-      visit namespace_project_commit_path(project.namespace, project, commit)
+      visit project_commit_path(project, commit)
 
       expect(page).to have_link(fred.to_reference)
     end
 
     it "renders title in repositories#branches" do
-      visit namespace_project_branches_path(project.namespace, project)
+      visit project_branches_path(project)
 
       expect(page).to have_link(issue.to_reference)
     end
@@ -51,12 +52,12 @@ describe "GitLab Flavored Markdown", feature: true do
   describe "for issues", feature: true, js: true do
     before do
       @other_issue = create(:issue,
-                            author: @user,
-                            assignees: [@user],
+                            author: user,
+                            assignees: [user],
                             project: project)
       @issue = create(:issue,
-                      author: @user,
-                      assignees: [@user],
+                      author: user,
+                      assignees: [user],
                       project: project,
                       title: "fix #{@other_issue.to_reference}",
                       description: "ask #{fred.to_reference} for details")
@@ -65,19 +66,19 @@ describe "GitLab Flavored Markdown", feature: true do
     end
 
     it "renders subject in issues#index" do
-      visit namespace_project_issues_path(project.namespace, project)
+      visit project_issues_path(project)
 
       expect(page).to have_link(@other_issue.to_reference)
     end
 
     it "renders subject in issues#show" do
-      visit namespace_project_issue_path(project.namespace, project, @issue)
+      visit project_issue_path(project, @issue)
 
       expect(page).to have_link(@other_issue.to_reference)
     end
 
     it "renders details in issues#show" do
-      visit namespace_project_issue_path(project.namespace, project, @issue)
+      visit project_issue_path(project, @issue)
 
       expect(page).to have_link(fred.to_reference)
     end
@@ -91,13 +92,13 @@ describe "GitLab Flavored Markdown", feature: true do
     end
 
     it "renders title in merge_requests#index" do
-      visit namespace_project_merge_requests_path(project.namespace, project)
+      visit project_merge_requests_path(project)
 
       expect(page).to have_link(issue.to_reference)
     end
 
     it "renders title in merge_requests#show" do
-      visit namespace_project_merge_request_path(project.namespace, project, @merge_request)
+      visit project_merge_request_path(project, @merge_request)
 
       expect(page).to have_link(issue.to_reference)
     end
@@ -112,19 +113,19 @@ describe "GitLab Flavored Markdown", feature: true do
     end
 
     it "renders title in milestones#index" do
-      visit namespace_project_milestones_path(project.namespace, project)
+      visit project_milestones_path(project)
 
       expect(page).to have_link(issue.to_reference)
     end
 
     it "renders title in milestones#show" do
-      visit namespace_project_milestone_path(project.namespace, project, @milestone)
+      visit project_milestone_path(project, @milestone)
 
       expect(page).to have_link(issue.to_reference)
     end
 
     it "renders description in milestones#show" do
-      visit namespace_project_milestone_path(project.namespace, project, @milestone)
+      visit project_milestone_path(project, @milestone)
 
       expect(page).to have_link(fred.to_reference)
     end
diff --git a/spec/features/global_search_spec.rb b/spec/features/global_search_spec.rb
index 4b22b07494d11101811915ea8f451a821cc2253b..54ebfe6cf77acab933a9367fa1d52281c5133299 100644
--- a/spec/features/global_search_spec.rb
+++ b/spec/features/global_search_spec.rb
@@ -6,7 +6,7 @@ feature 'Global search', feature: true do
 
   before do
     project.team << [user, :master]
-    login_with(user)
+    gitlab_sign_in(user)
   end
 
   describe 'I search through the issues and I see pagination' do
diff --git a/spec/features/groups/activity_spec.rb b/spec/features/groups/activity_spec.rb
index 81f9c103e954beac541c7d91e3580684c2862bf9..9f66a3d8c72337943516cc68d28bece76959905a 100644
--- a/spec/features/groups/activity_spec.rb
+++ b/spec/features/groups/activity_spec.rb
@@ -7,7 +7,7 @@ feature 'Group activity page', feature: true do
   context 'when signed in' do
     before do
       user = create(:group_member, :developer, user: create(:user), group: group ).user
-      login_as(user)
+      gitlab_sign_in(user)
       visit path
     end
 
diff --git a/spec/features/groups/empty_states_spec.rb b/spec/features/groups/empty_states_spec.rb
index fef8e41bffe1692dbae4ce6e73670016941c2493..b1c7151dfa86c13e8d5bc9460cf90005ba0d0f29 100644
--- a/spec/features/groups/empty_states_spec.rb
+++ b/spec/features/groups/empty_states_spec.rb
@@ -5,7 +5,7 @@ feature 'Groups Merge Requests Empty States' do
   let(:user) { create(:group_member, :developer, user: create(:user), group: group ).user }
 
   before do
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   context 'group has a project' do
diff --git a/spec/features/groups/group_name_toggle_spec.rb b/spec/features/groups/group_name_toggle_spec.rb
index dfc3c84f29af23c0d3489f4e25deed2069a99d49..f450626c37076843ea527a4d05df2eb4591b2e6d 100644
--- a/spec/features/groups/group_name_toggle_spec.rb
+++ b/spec/features/groups/group_name_toggle_spec.rb
@@ -9,7 +9,7 @@ feature 'Group name toggle', feature: true, js: true do
   SMALL_SCREEN = 300
 
   before do
-    login_as :user
+    gitlab_sign_in :user
   end
 
   it 'is not present if enough horizontal space' do
diff --git a/spec/features/groups/group_settings_spec.rb b/spec/features/groups/group_settings_spec.rb
index 6afde1d0bed31b0f84c9e2e8a173100680f885bd..56e163ec4d0259a8ae8787f9bb6852b8ab106bc3 100644
--- a/spec/features/groups/group_settings_spec.rb
+++ b/spec/features/groups/group_settings_spec.rb
@@ -6,7 +6,7 @@ feature 'Edit group settings', feature: true do
 
   background do
     group.add_owner(user)
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   describe 'when the group path is changed' do
@@ -18,14 +18,14 @@ feature 'Edit group settings', feature: true do
       update_path(new_group_path)
       visit new_group_full_path
       expect(current_path).to eq(new_group_full_path)
-      expect(find('h1.group-title')).to have_content(new_group_path)
+      expect(find('h1.group-title')).to have_content(group.name)
     end
 
     scenario 'the old group path redirects to the new path' do
       update_path(new_group_path)
       visit old_group_full_path
       expect(current_path).to eq(new_group_full_path)
-      expect(find('h1.group-title')).to have_content(new_group_path)
+      expect(find('h1.group-title')).to have_content(group.name)
     end
 
     context 'with a subgroup' do
@@ -37,14 +37,14 @@ feature 'Edit group settings', feature: true do
         update_path(new_group_path)
         visit new_subgroup_full_path
         expect(current_path).to eq(new_subgroup_full_path)
-        expect(find('h1.group-title')).to have_content(subgroup.path)
+        expect(find('h1.group-title')).to have_content(subgroup.name)
       end
 
       scenario 'the old subgroup path redirects to the new path' do
         update_path(new_group_path)
         visit old_subgroup_full_path
         expect(current_path).to eq(new_subgroup_full_path)
-        expect(find('h1.group-title')).to have_content(subgroup.path)
+        expect(find('h1.group-title')).to have_content(subgroup.name)
       end
     end
 
diff --git a/spec/features/groups/labels/edit_spec.rb b/spec/features/groups/labels/edit_spec.rb
index 69281cecb7b4251969529fb5ff95ddae653512c6..b33040ef843a662d590f946a7f17f79f4b2fe289 100644
--- a/spec/features/groups/labels/edit_spec.rb
+++ b/spec/features/groups/labels/edit_spec.rb
@@ -7,7 +7,7 @@ feature 'Edit group label', feature: true do
 
   background do
     group.add_owner(user)
-    login_as(user)
+    gitlab_sign_in(user)
     visit edit_group_label_path(group, label)
   end
 
diff --git a/spec/features/groups/labels/subscription_spec.rb b/spec/features/groups/labels/subscription_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8b891c52d08beb54a32a8c90bee34658def765a8
--- /dev/null
+++ b/spec/features/groups/labels/subscription_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+
+feature 'Labels subscription', feature: true do
+  let(:user)     { create(:user) }
+  let(:group)    { create(:group) }
+  let!(:feature) { create(:group_label, group: group, title: 'feature') }
+
+  context 'when signed in' do
+    before do
+      group.add_developer(user)
+      gitlab_sign_in user
+    end
+
+    scenario 'users can subscribe/unsubscribe to group labels', js: true do
+      visit group_labels_path(group)
+
+      expect(page).to have_content('feature')
+
+      within "#group_label_#{feature.id}" do
+        expect(page).not_to have_button 'Unsubscribe'
+
+        click_button 'Subscribe'
+
+        expect(page).not_to have_button 'Subscribe'
+        expect(page).to have_button 'Unsubscribe'
+
+        click_button 'Unsubscribe'
+
+        expect(page).to have_button 'Subscribe'
+        expect(page).not_to have_button 'Unsubscribe'
+      end
+    end
+  end
+
+  context 'when not signed in' do
+    it 'users can not subscribe/unsubscribe to labels' do
+      visit group_labels_path(group)
+
+      expect(page).to have_content 'feature'
+      expect(page).not_to have_button('Subscribe')
+    end
+  end
+
+  def click_link_on_dropdown(text)
+    find('.dropdown-group-label').click
+
+    page.within('.dropdown-group-label') do
+      find('a.js-subscribe-button', text: text).click
+    end
+  end
+end
diff --git a/spec/features/groups/members/last_owner_cannot_leave_group_spec.rb b/spec/features/groups/members/last_owner_cannot_leave_group_spec.rb
deleted file mode 100644
index be60b0489c7a9643fdec5336ee6a7a504e1f97da..0000000000000000000000000000000000000000
--- a/spec/features/groups/members/last_owner_cannot_leave_group_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-require 'spec_helper'
-
-feature 'Groups > Members > Last owner cannot leave group', feature: true do
-  let(:owner) { create(:user) }
-  let(:group) { create(:group) }
-
-  background do
-    group.add_owner(owner)
-    login_as(owner)
-    visit group_path(group)
-  end
-
-  scenario 'user does not see a "Leave group" link' do
-    expect(page).not_to have_content 'Leave group'
-  end
-end
diff --git a/spec/features/groups/members/leave_group_spec.rb b/spec/features/groups/members/leave_group_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b438f57753c6b5beee60237c47692dae9cf2cf08
--- /dev/null
+++ b/spec/features/groups/members/leave_group_spec.rb
@@ -0,0 +1,62 @@
+require 'spec_helper'
+
+feature 'Groups > Members > Leave group', feature: true do
+  let(:user) { create(:user) }
+  let(:other_user) { create(:user) }
+  let(:group) { create(:group) }
+
+  background do
+    gitlab_sign_in(user)
+  end
+
+  scenario 'guest leaves the group' do
+    group.add_guest(user)
+    group.add_owner(other_user)
+
+    visit group_path(group)
+    click_link 'Leave group'
+
+    expect(current_path).to eq(dashboard_groups_path)
+    expect(page).to have_content left_group_message(group)
+    expect(group.users).not_to include(user)
+  end
+
+  scenario 'guest leaves the group as last member' do
+    group.add_guest(user)
+
+    visit group_path(group)
+    click_link 'Leave group'
+
+    expect(current_path).to eq(dashboard_groups_path)
+    expect(page).to have_content left_group_message(group)
+    expect(group.users).not_to include(user)
+  end
+
+  scenario 'owner leaves the group if they is not the last owner' do
+    group.add_owner(user)
+    group.add_owner(other_user)
+
+    visit group_path(group)
+    click_link 'Leave group'
+
+    expect(current_path).to eq(dashboard_groups_path)
+    expect(page).to have_content left_group_message(group)
+    expect(group.users).not_to include(user)
+  end
+
+  scenario 'owner can not leave the group if they is a last owner' do
+    group.add_owner(user)
+
+    visit group_path(group)
+
+    expect(page).not_to have_content 'Leave group'
+
+    visit group_group_members_path(group)
+
+    expect(find(:css, '.project-members-page li', text: user.name)).not_to have_selector(:css, 'a.btn-remove')
+  end
+
+  def left_group_message(group)
+    "You left the \"#{group.name}\""
+  end
+end
diff --git a/spec/features/groups/members/list_members_spec.rb b/spec/features/groups/members/list_members_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f6493c4c50e1608cc76c6bd4bea3ba2219187171
--- /dev/null
+++ b/spec/features/groups/members/list_members_spec.rb
@@ -0,0 +1,42 @@
+require 'spec_helper'
+
+feature 'Groups > Members > List members', feature: true do
+  include Select2Helper
+
+  let(:user1) { create(:user, name: 'John Doe') }
+  let(:user2) { create(:user, name: 'Mary Jane') }
+  let(:group) { create(:group) }
+  let(:nested_group) { create(:group, parent: group) }
+
+  background do
+    gitlab_sign_in(user1)
+  end
+
+  scenario 'show members from current group and parent', :nested_groups do
+    group.add_developer(user1)
+    nested_group.add_developer(user2)
+
+    visit group_group_members_path(nested_group)
+
+    expect(first_row.text).to include(user1.name)
+    expect(second_row.text).to include(user2.name)
+  end
+
+  scenario 'show user once if member of both current group and parent', :nested_groups do
+    group.add_developer(user1)
+    nested_group.add_developer(user1)
+
+    visit group_group_members_path(nested_group)
+
+    expect(first_row.text).to include(user1.name)
+    expect(second_row).to be_blank
+  end
+
+  def first_row
+    page.all('ul.content-list > li')[0]
+  end
+
+  def second_row
+    page.all('ul.content-list > li')[1]
+  end
+end
diff --git a/spec/features/groups/members/owner_manages_access_requests_spec.rb b/spec/features/groups/members/manage_access_requests_spec.rb
similarity index 88%
rename from spec/features/groups/members/owner_manages_access_requests_spec.rb
rename to spec/features/groups/members/manage_access_requests_spec.rb
index dbe150823ba1e1013696359056ce90c6f40bf2d0..f84d8594c65292ca73d84fbf2ee94df683b406fe 100644
--- a/spec/features/groups/members/owner_manages_access_requests_spec.rb
+++ b/spec/features/groups/members/manage_access_requests_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-feature 'Groups > Members > Owner manages access requests', feature: true do
+feature 'Groups > Members > Manage access requests', feature: true do
   let(:user) { create(:user) }
   let(:owner) { create(:user) }
   let(:group) { create(:group, :public, :access_requestable) }
@@ -8,7 +8,7 @@ feature 'Groups > Members > Owner manages access requests', feature: true do
   background do
     group.request_access(user)
     group.add_owner(owner)
-    login_as(owner)
+    gitlab_sign_in(owner)
   end
 
   scenario 'owner can see access requests' do
@@ -17,7 +17,7 @@ feature 'Groups > Members > Owner manages access requests', feature: true do
     expect_visible_access_request(group, user)
   end
 
-  scenario 'master can grant access' do
+  scenario 'owner can grant access' do
     visit group_group_members_path(group)
 
     expect_visible_access_request(group, user)
@@ -28,7 +28,7 @@ feature 'Groups > Members > Owner manages access requests', feature: true do
     expect(ActionMailer::Base.deliveries.last.subject).to match "Access to the #{group.name} group was granted"
   end
 
-  scenario 'master can deny access' do
+  scenario 'owner can deny access' do
     visit group_group_members_path(group)
 
     expect_visible_access_request(group, user)
diff --git a/spec/features/groups/members/list_spec.rb b/spec/features/groups/members/manage_members.rb
similarity index 68%
rename from spec/features/groups/members/list_spec.rb
rename to spec/features/groups/members/manage_members.rb
index f654fa16a0670bc861c913eed996379146609c54..a9a654b20e2f66a68c903fb7276eee5d6e02a91f 100644
--- a/spec/features/groups/members/list_spec.rb
+++ b/spec/features/groups/members/manage_members.rb
@@ -1,35 +1,14 @@
 require 'spec_helper'
 
-feature 'Groups members list', feature: true do
+feature 'Groups > Members > Manage members', feature: true do
   include Select2Helper
 
   let(:user1) { create(:user, name: 'John Doe') }
   let(:user2) { create(:user, name: 'Mary Jane') }
   let(:group) { create(:group) }
-  let(:nested_group) { create(:group, parent: group) }
 
   background do
-    login_as(user1)
-  end
-
-  scenario 'show members from current group and parent', :nested_groups do
-    group.add_developer(user1)
-    nested_group.add_developer(user2)
-
-    visit group_group_members_path(nested_group)
-
-    expect(first_row.text).to include(user1.name)
-    expect(second_row.text).to include(user2.name)
-  end
-
-  scenario 'show user once if member of both current group and parent', :nested_groups do
-    group.add_developer(user1)
-    nested_group.add_developer(user1)
-
-    visit group_group_members_path(nested_group)
-
-    expect(first_row.text).to include(user1.name)
-    expect(second_row).to be_blank
+    gitlab_sign_in(user1)
   end
 
   scenario 'update user to owner level', :js do
@@ -59,6 +38,18 @@ feature 'Groups members list', feature: true do
     end
   end
 
+  scenario 'remove user from group', :js do
+    group.add_owner(user1)
+    group.add_developer(user2)
+
+    visit group_group_members_path(group)
+
+    find(:css, '.project-members-page li', text: user2.name).find(:css, 'a.btn-remove').click
+
+    expect(page).not_to have_content(user2.name)
+    expect(group.users).not_to include(user2)
+  end
+
   scenario 'add yourself to group when already an owner', :js do
     group.add_owner(user1)
 
@@ -86,6 +77,23 @@ feature 'Groups members list', feature: true do
     end
   end
 
+  scenario 'guest can not manage other users' do
+    group.add_guest(user1)
+    group.add_developer(user2)
+
+    visit group_group_members_path(group)
+
+    expect(page).not_to have_button 'Add to group'
+
+    page.within(second_row) do
+      # Can not modify user2 role
+      expect(page).not_to have_button 'Developer'
+
+      # Can not remove user2
+      expect(page).not_to have_css('a.btn-remove')
+    end
+  end
+
   def first_row
     page.all('ul.content-list > li')[0]
   end
diff --git a/spec/features/groups/members/member_cannot_request_access_to_his_project_spec.rb b/spec/features/groups/members/member_cannot_request_access_to_his_project_spec.rb
deleted file mode 100644
index 37c433cc09a886abb55a695c667edb582fef3998..0000000000000000000000000000000000000000
--- a/spec/features/groups/members/member_cannot_request_access_to_his_project_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-require 'spec_helper'
-
-feature 'Groups > Members > Member cannot request access to his project', feature: true do
-  let(:member) { create(:user) }
-  let(:group) { create(:group) }
-
-  background do
-    group.add_developer(member)
-    login_as(member)
-    visit group_path(group)
-  end
-
-  scenario 'member does not see the request access button' do
-    expect(page).not_to have_content 'Request Access'
-  end
-end
diff --git a/spec/features/groups/members/member_leaves_group_spec.rb b/spec/features/groups/members/member_leaves_group_spec.rb
deleted file mode 100644
index ac4d94658ae85d480e1b6d1f289c360ecd6093ed..0000000000000000000000000000000000000000
--- a/spec/features/groups/members/member_leaves_group_spec.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-require 'spec_helper'
-
-feature 'Groups > Members > Member leaves group', feature: true do
-  let(:user) { create(:user) }
-  let(:owner) { create(:user) }
-  let(:group) { create(:group, :public) }
-
-  background do
-    group.add_owner(owner)
-    group.add_developer(user)
-    login_as(user)
-    visit group_path(group)
-  end
-
-  scenario 'user leaves group' do
-    click_link 'Leave group'
-
-    expect(current_path).to eq(dashboard_groups_path)
-    expect(group.users.exists?(user.id)).to be_falsey
-  end
-end
diff --git a/spec/features/groups/members/user_requests_access_spec.rb b/spec/features/groups/members/request_access_spec.rb
similarity index 88%
rename from spec/features/groups/members/user_requests_access_spec.rb
rename to spec/features/groups/members/request_access_spec.rb
index e4b5ea91bd3ab27faa15c159b65c16ee82e1aacb..41c31b62e18ad9b281dc52067cad452a78de5b6e 100644
--- a/spec/features/groups/members/user_requests_access_spec.rb
+++ b/spec/features/groups/members/request_access_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-feature 'Groups > Members > User requests access', feature: true do
+feature 'Groups > Members > Request access', feature: true do
   let(:user) { create(:user) }
   let(:owner) { create(:user) }
   let(:group) { create(:group, :public, :access_requestable) }
@@ -8,7 +8,7 @@ feature 'Groups > Members > User requests access', feature: true do
 
   background do
     group.add_owner(owner)
-    login_as(user)
+    gitlab_sign_in(user)
     visit group_path(group)
   end
 
@@ -68,4 +68,11 @@ feature 'Groups > Members > User requests access', feature: true do
     expect(group.requesters.exists?(user_id: user)).to be_falsey
     expect(page).to have_content 'Your access request to the group has been withdrawn.'
   end
+
+  scenario 'member does not see the request access button' do
+    group.add_owner(user)
+    visit group_path(group)
+
+    expect(page).not_to have_content 'Request Access'
+  end
 end
diff --git a/spec/features/groups/members/sorting_spec.rb b/spec/features/groups/members/sort_members_spec.rb
similarity index 97%
rename from spec/features/groups/members/sorting_spec.rb
rename to spec/features/groups/members/sort_members_spec.rb
index 902d3f789ff1428e8d66341d4c4dd9df79b23425..8ee6195384480c0471dded63932fde39610c9a4d 100644
--- a/spec/features/groups/members/sorting_spec.rb
+++ b/spec/features/groups/members/sort_members_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-feature 'Groups > Members > Sorting', feature: true do
+feature 'Groups > Members > Sort members', feature: true do
   let(:owner)     { create(:user, name: 'John Doe') }
   let(:developer) { create(:user, name: 'Mary Jane', last_sign_in_at: 5.days.ago) }
   let(:group)     { create(:group) }
@@ -9,7 +9,7 @@ feature 'Groups > Members > Sorting', feature: true do
     create(:group_member, :owner, user: owner, group: group, created_at: 5.days.ago)
     create(:group_member, :developer, user: developer, group: group, created_at: 3.days.ago)
 
-    login_as(owner)
+    gitlab_sign_in(owner)
   end
 
   scenario 'sorts alphabetically by default' do
diff --git a/spec/features/groups/milestone_spec.rb b/spec/features/groups/milestone_spec.rb
index daa2c6afd63410ee58e076913412fc2ef5ce4dfc..330310eae6b6a5601a365ed71ba0e6f529d59140 100644
--- a/spec/features/groups/milestone_spec.rb
+++ b/spec/features/groups/milestone_spec.rb
@@ -8,7 +8,7 @@ feature 'Group milestones', :feature, :js do
   before do
     Timecop.freeze
 
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   after do
diff --git a/spec/features/groups/show_spec.rb b/spec/features/groups/show_spec.rb
index d3c49c37374802a60a69cfedf8163ca621bde2cb..76575f61528863504179f3bfafc52cb96903d7d7 100644
--- a/spec/features/groups/show_spec.rb
+++ b/spec/features/groups/show_spec.rb
@@ -7,7 +7,7 @@ feature 'Group show page', feature: true do
   context 'when signed in' do
     before do
       user = create(:group_member, :developer, user: create(:user), group: group ).user
-      login_as(user)
+      gitlab_sign_in(user)
       visit path
     end
 
diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb
index 5737ca39b4ef53a813263b93706f760676dd7031..c1dc7be70880986dfd44e3325988057a8cb7015f 100644
--- a/spec/features/groups_spec.rb
+++ b/spec/features/groups_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 feature 'Group', feature: true do
   before do
-    login_as(:admin)
+    gitlab_sign_in(:admin)
   end
 
   matcher :have_namespace_error_message do
@@ -108,8 +108,8 @@ feature 'Group', feature: true do
 
       before do
         group.add_owner(user)
-        logout
-        login_as(user)
+        gitlab_sign_out
+        gitlab_sign_in(user)
 
         visit subgroups_group_path(group)
         click_link 'New Subgroup'
@@ -128,14 +128,14 @@ feature 'Group', feature: true do
   it 'checks permissions to avoid exposing groups by parent_id' do
     group = create(:group, :private, path: 'secret-group')
 
-    logout
-    login_as(:user)
+    gitlab_sign_out
+    gitlab_sign_in(:user)
     visit new_group_path(parent_id: group.id)
 
     expect(page).not_to have_content('secret-group')
   end
 
-  describe 'group edit' do
+  describe 'group edit', js: true do
     let(:group) { create(:group) }
     let(:path)  { edit_group_path(group) }
     let(:new_name) { 'new-name' }
@@ -157,8 +157,8 @@ feature 'Group', feature: true do
     end
 
     it 'removes group' do
-      click_link 'Remove group'
-
+      expect { remove_with_confirm('Remove group', group.path) }.to change {Group.count}.by(-1)
+      expect(group.members.all.count).to be_zero
       expect(page).to have_content "scheduled for deletion"
     end
   end
@@ -212,4 +212,10 @@ feature 'Group', feature: true do
       expect(page).to have_content(nested_group.name)
     end
   end
+
+  def remove_with_confirm(button_text, confirm_with)
+    click_button button_text
+    fill_in 'confirm_name_input', with: confirm_with
+    click_button 'Confirm'
+  end
 end
diff --git a/spec/features/help_pages_spec.rb b/spec/features/help_pages_spec.rb
index 18102146b5f6155b4838ae2fd4689a5e3a9c6c70..b01ee1cf491979fbd71f14572f3da13b0854fc71 100644
--- a/spec/features/help_pages_spec.rb
+++ b/spec/features/help_pages_spec.rb
@@ -40,7 +40,7 @@ describe 'Help Pages', feature: true do
       allow_any_instance_of(ApplicationSetting).to receive(:version_check_enabled) { true }
       allow_any_instance_of(VersionCheck).to receive(:url) { '/version-check-url' }
 
-      login_as :user
+      gitlab_sign_in :user
       visit help_path
     end
 
@@ -60,7 +60,7 @@ describe 'Help Pages', feature: true do
       allow_any_instance_of(ApplicationSetting).to receive(:help_page_text) { "My Custom Text" }
       allow_any_instance_of(ApplicationSetting).to receive(:help_page_support_url) { "http://example.com/help" }
 
-      login_as :user
+      gitlab_sign_in(:user)
       visit help_path
     end
 
diff --git a/spec/features/issuables/issuable_list_spec.rb b/spec/features/issuables/issuable_list_spec.rb
index 414838fa22e3c7de30cb434a4187f9b1bb244423..5046bfb59492381c44ce0c1bc2f498950de60a34 100644
--- a/spec/features/issuables/issuable_list_spec.rb
+++ b/spec/features/issuables/issuable_list_spec.rb
@@ -8,7 +8,7 @@ describe 'issuable list', feature: true do
 
   before do
     project.add_user(user, :developer)
-    login_as(user)
+    gitlab_sign_in(user)
     issuable_types.each { |type| create_issuables(type) }
   end
 
@@ -39,9 +39,9 @@ describe 'issuable list', feature: true do
 
   def visit_issuable_list(issuable_type)
     if issuable_type == :issue
-      visit namespace_project_issues_path(project.namespace, project)
+      visit project_issues_path(project)
     else
-      visit namespace_project_merge_requests_path(project.namespace, project)
+      visit project_merge_requests_path(project)
     end
   end
 
diff --git a/spec/features/issuables/user_sees_sidebar_spec.rb b/spec/features/issuables/user_sees_sidebar_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..948d151a517ec362d5be0f96ab57211661c0a418
--- /dev/null
+++ b/spec/features/issuables/user_sees_sidebar_spec.rb
@@ -0,0 +1,30 @@
+require 'rails_helper'
+
+describe 'Issue Sidebar on Mobile' do
+  include MobileHelpers
+
+  let(:project) { create(:project, :public) }
+  let(:merge_request) { create(:merge_request, source_project: project) }
+  let(:issue) { create(:issue, project: project) }
+  let!(:user) { create(:user)}
+
+  before do
+    sign_in(user)
+  end
+
+  context 'mobile sidebar on merge requests', js: true do
+    before do
+      visit project_merge_request_path(merge_request.project, merge_request)
+    end
+
+    it_behaves_like "issue sidebar stays collapsed on mobile"
+  end
+
+  context 'mobile sidebar on issues', js: true do
+    before do
+      visit project_issue_path(project, issue)
+    end
+
+    it_behaves_like "issue sidebar stays collapsed on mobile"
+  end
+end
diff --git a/spec/features/issues/award_emoji_spec.rb b/spec/features/issues/award_emoji_spec.rb
index 81ae54c7a108caf7e548488a7af21b170ca4ae3c..2c84965f7f3357562d80bc84715fb8b761c2ff33 100644
--- a/spec/features/issues/award_emoji_spec.rb
+++ b/spec/features/issues/award_emoji_spec.rb
@@ -12,14 +12,14 @@ describe 'Awards Emoji', feature: true do
   context 'authorized user' do
     before do
       project.team << [user, :master]
-      login_as(user)
+      gitlab_sign_in(user)
     end
 
     describe 'visiting an issue with a legacy award emoji that is not valid anymore' do
       before do
         # The `heart_tip` emoji is not valid anymore so we need to skip validation
         issue.award_emoji.build(user: user, name: 'heart_tip').save!(validate: false)
-        visit namespace_project_issue_path(project.namespace, project, issue)
+        visit project_issue_path(project, issue)
         wait_for_requests
       end
 
@@ -33,7 +33,7 @@ describe 'Awards Emoji', feature: true do
       let!(:note) { create(:note_on_issue, noteable: issue, project: issue.project, note: "Hello world") }
 
       before do
-        visit namespace_project_issue_path(project.namespace, project, issue)
+        visit project_issue_path(project, issue)
         wait_for_requests
       end
 
@@ -81,13 +81,13 @@ describe 'Awards Emoji', feature: true do
         end
       end
 
-      context 'execute /award slash command' do
+      context 'execute /award quick action' do
         it 'toggles the emoji award on noteable', js: true do
-          execute_slash_command('/award :100:')
+          execute_quick_action('/award :100:')
 
           expect(find(noteable_award_counter)).to have_text("1")
 
-          execute_slash_command('/award :100:')
+          execute_quick_action('/award :100:')
 
           expect(page).not_to have_selector(noteable_award_counter)
         end
@@ -97,7 +97,7 @@ describe 'Awards Emoji', feature: true do
 
   context 'unauthorized user', js: true do
     before do
-      visit namespace_project_issue_path(project.namespace, project, issue)
+      visit project_issue_path(project, issue)
     end
 
     it 'has disabled emoji button' do
@@ -105,7 +105,7 @@ describe 'Awards Emoji', feature: true do
     end
   end
 
-  def execute_slash_command(cmd)
+  def execute_quick_action(cmd)
     within('.js-main-target-form') do
       fill_in 'note[note]', with: cmd
       click_button 'Comment'
diff --git a/spec/features/issues/award_spec.rb b/spec/features/issues/award_spec.rb
index fcf22dd50330a86595fcb3132e24f7497f5f0737..62c5fce81b6db67d3564fc79fc3b808a8bbd921c 100644
--- a/spec/features/issues/award_spec.rb
+++ b/spec/features/issues/award_spec.rb
@@ -7,8 +7,8 @@ feature 'Issue awards', js: true, feature: true do
 
   describe 'logged in' do
     before do
-      login_as(user)
-      visit namespace_project_issue_path(project.namespace, project, issue)
+      gitlab_sign_in(user)
+      visit project_issue_path(project, issue)
       wait_for_requests
     end
 
@@ -17,7 +17,7 @@ feature 'Issue awards', js: true, feature: true do
       expect(page).to have_selector('.js-emoji-btn.active')
       expect(first('.js-emoji-btn')).to have_content '1'
 
-      visit namespace_project_issue_path(project.namespace, project, issue)
+      visit project_issue_path(project, issue)
       expect(first('.js-emoji-btn')).to have_content '1'
     end
 
@@ -26,7 +26,7 @@ feature 'Issue awards', js: true, feature: true do
       find('.js-emoji-btn.active').click
       expect(first('.js-emoji-btn')).to have_content '0'
 
-      visit namespace_project_issue_path(project.namespace, project, issue)
+      visit project_issue_path(project, issue)
       expect(first('.js-emoji-btn')).to have_content '0'
     end
 
@@ -40,7 +40,7 @@ feature 'Issue awards', js: true, feature: true do
 
   describe 'logged out' do
     before do
-      visit namespace_project_issue_path(project.namespace, project, issue)
+      visit project_issue_path(project, issue)
       wait_for_requests
     end
 
diff --git a/spec/features/issues/bulk_assignment_labels_spec.rb b/spec/features/issues/bulk_assignment_labels_spec.rb
index 95b4930cd321d7b2a7c1b2a500b92610b4296fde..86226d97f79e5a1351795311a29d5fa3fba347d4 100644
--- a/spec/features/issues/bulk_assignment_labels_spec.rb
+++ b/spec/features/issues/bulk_assignment_labels_spec.rb
@@ -13,7 +13,22 @@ feature 'Issues > Labels bulk assignment', feature: true do
     before do
       project.team << [user, :master]
 
-      login_as user
+      gitlab_sign_in user
+    end
+
+    context 'sidebar' do
+      before do
+        enable_bulk_update
+      end
+
+      it 'is present when bulk edit is enabled' do
+        expect(page).to have_css('.issuable-sidebar')
+      end
+
+      it 'is not present when bulk edit is disabled' do
+        disable_bulk_update
+        expect(page).not_to have_css('.issuable-sidebar')
+      end
     end
 
     context 'can bulk assign' do
@@ -331,9 +346,9 @@ feature 'Issues > Labels bulk assignment', feature: true do
 
   context 'as a guest' do
     before do
-      login_as user
+      gitlab_sign_in user
 
-      visit namespace_project_issues_path(project.namespace, project)
+      visit project_issues_path(project)
     end
 
     context 'cannot bulk assign labels' do
@@ -395,7 +410,11 @@ feature 'Issues > Labels bulk assignment', feature: true do
   end
 
   def enable_bulk_update
-    visit namespace_project_issues_path(project.namespace, project)
+    visit project_issues_path(project)
     click_button 'Edit Issues'
   end
+
+  def disable_bulk_update
+    click_button 'Cancel'
+  end
 end
diff --git a/spec/features/issues/create_branch_merge_request_spec.rb b/spec/features/issues/create_branch_merge_request_spec.rb
index 1d7d8d291b29264025dc61a53af0003ada2db421..f730141f82c023f6eebf894ad2f47620b71da90b 100644
--- a/spec/features/issues/create_branch_merge_request_spec.rb
+++ b/spec/features/issues/create_branch_merge_request_spec.rb
@@ -8,11 +8,11 @@ feature 'Create Branch/Merge Request Dropdown on issue page', feature: true, js:
   context 'for team members' do
     before do
       project.team << [user, :developer]
-      login_as(user)
+      gitlab_sign_in(user)
     end
 
     it 'allows creating a merge request from the issue page' do
-      visit namespace_project_issue_path(project.namespace, project, issue)
+      visit project_issue_path(project, issue)
 
       select_dropdown_option('create-mr')
 
@@ -21,21 +21,21 @@ feature 'Create Branch/Merge Request Dropdown on issue page', feature: true, js:
       expect(page).to have_content("created branch 1-cherry-coloured-funk")
       expect(page).to have_content("mentioned in merge request !1")
 
-      visit namespace_project_merge_request_path(project.namespace, project, MergeRequest.first)
+      visit project_merge_request_path(project, MergeRequest.first)
 
       expect(page).to have_content('WIP: Resolve "Cherry-Coloured Funk"')
-      expect(current_path).to eq(namespace_project_merge_request_path(project.namespace, project, MergeRequest.first))
+      expect(current_path).to eq(project_merge_request_path(project, MergeRequest.first))
     end
 
     it 'allows creating a branch from the issue page' do
-      visit namespace_project_issue_path(project.namespace, project, issue)
+      visit project_issue_path(project, issue)
 
       select_dropdown_option('create-branch')
 
       wait_for_requests
 
       expect(page).to have_selector('.dropdown-toggle-text ', text: '1-cherry-coloured-funk')
-      expect(current_path).to eq namespace_project_tree_path(project.namespace, project, '1-cherry-coloured-funk')
+      expect(current_path).to eq project_tree_path(project, '1-cherry-coloured-funk')
     end
 
     context "when there is a referenced merge request" do
@@ -52,7 +52,7 @@ feature 'Create Branch/Merge Request Dropdown on issue page', feature: true, js:
       before do
         referenced_mr.cache_merge_request_closes_issues!(user)
 
-        visit namespace_project_issue_path(project.namespace, project, issue)
+        visit project_issue_path(project, issue)
       end
 
       it 'disables the create branch button' do
@@ -66,7 +66,7 @@ feature 'Create Branch/Merge Request Dropdown on issue page', feature: true, js:
       it 'disables the create branch button' do
         issue = create(:issue, :confidential, project: project)
 
-        visit namespace_project_issue_path(project.namespace, project, issue)
+        visit project_issue_path(project, issue)
 
         expect(page).not_to have_css('.create-mr-dropdown-wrap')
       end
@@ -75,7 +75,7 @@ feature 'Create Branch/Merge Request Dropdown on issue page', feature: true, js:
 
   context 'for visitors' do
     before do
-      visit namespace_project_issue_path(project.namespace, project, issue)
+      visit project_issue_path(project, issue)
     end
 
     it 'shows no buttons' do
diff --git a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb
index 24e2419b5ce1fe1ae87e2a28c507328082ed8b7d..3b7622882c17e694b3a1600f4ada9921d14d7fb9 100644
--- a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb
+++ b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb
@@ -9,13 +9,13 @@ feature 'Resolving all open discussions in a merge request from an issue', featu
   describe 'as a user with access to the project' do
     before do
       project.team << [user, :master]
-      login_as user
-      visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+      gitlab_sign_in user
+      visit project_merge_request_path(project, merge_request)
     end
 
     it 'shows a button to resolve all discussions by creating a new issue' do
       within('#resolve-count-app') do
-        expect(page).to have_link "Resolve all discussions in new issue", href: new_namespace_project_issue_path(project.namespace, project, merge_request_to_resolve_discussions_of: merge_request.iid)
+        expect(page).to have_link "Resolve all discussions in new issue", href: new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid)
       end
     end
 
@@ -25,13 +25,13 @@ feature 'Resolving all open discussions in a merge request from an issue', featu
       end
 
       it 'hides the link for creating a new issue' do
-        expect(page).not_to have_link "Resolve all discussions in new issue", href: new_namespace_project_issue_path(project.namespace, project, merge_request_to_resolve_discussions_of: merge_request.iid)
+        expect(page).not_to have_link "Resolve all discussions in new issue", href: new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid)
       end
     end
 
     context 'creating an issue for discussions' do
       before do
-        click_link "Resolve all discussions in new issue", href: new_namespace_project_issue_path(project.namespace, project, merge_request_to_resolve_discussions_of: merge_request.iid)
+        click_link "Resolve all discussions in new issue", href: new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid)
       end
 
       it_behaves_like 'creating an issue for a discussion'
@@ -45,7 +45,7 @@ feature 'Resolving all open discussions in a merge request from an issue', featu
       context 'with the internal tracker disabled' do
         before do
           project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
-          visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+          visit project_merge_request_path(project, merge_request)
         end
 
         it 'does not show a link to create a new issue' do
@@ -55,7 +55,7 @@ feature 'Resolving all open discussions in a merge request from an issue', featu
 
       context 'merge request has discussions that need to be resolved' do
         before do
-          visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+          visit project_merge_request_path(project, merge_request)
         end
 
         it 'shows a warning that the merge request contains unresolved discussions' do
@@ -64,13 +64,13 @@ feature 'Resolving all open discussions in a merge request from an issue', featu
 
         it 'has a link to resolve all discussions by creating an issue' do
           page.within '.mr-widget-body' do
-            expect(page).to have_link 'Create an issue to resolve them later', href: new_namespace_project_issue_path(project.namespace, project, merge_request_to_resolve_discussions_of: merge_request.iid)
+            expect(page).to have_link 'Create an issue to resolve them later', href: new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid)
           end
         end
 
         context 'creating an issue for discussions' do
           before do
-            page.click_link 'Create an issue to resolve them later', href: new_namespace_project_issue_path(project.namespace, project, merge_request_to_resolve_discussions_of: merge_request.iid)
+            page.click_link 'Create an issue to resolve them later', href: new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid)
           end
 
           it_behaves_like 'creating an issue for a discussion'
@@ -82,8 +82,8 @@ feature 'Resolving all open discussions in a merge request from an issue', featu
   describe 'as a reporter' do
     before do
       project.team << [user, :reporter]
-      login_as user
-      visit new_namespace_project_issue_path(project.namespace, project, merge_request_to_resolve_discussions_of: merge_request.iid)
+      gitlab_sign_in user
+      visit new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid)
     end
 
     it 'Shows a notice to ask someone else to resolve the discussions' do
diff --git a/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb b/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb
index 3a5a79e03f4082779ff5ebcbab9d609a41980dd9..97d49184920a4c273b4fed130f41fa1036b832e9 100644
--- a/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb
+++ b/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb
@@ -9,14 +9,14 @@ feature 'Resolve an open discussion in a merge request by creating an issue', fe
   describe 'As a user with access to the project' do
     before do
       project.team << [user, :master]
-      login_as user
-      visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+      gitlab_sign_in user
+      visit project_merge_request_path(project, merge_request)
     end
 
     context 'with the internal tracker disabled' do
       before do
         project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
-        visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+        visit project_merge_request_path(project, merge_request)
       end
 
       it 'does not show a link to create a new issue' do
@@ -43,14 +43,14 @@ feature 'Resolve an open discussion in a merge request by creating an issue', fe
     end
 
     it 'has a link to create a new issue for a discussion' do
-      new_issue_link = new_namespace_project_issue_path(project.namespace, project, discussion_to_resolve: discussion.id, merge_request_to_resolve_discussions_of: merge_request.iid)
+      new_issue_link = new_project_issue_path(project, discussion_to_resolve: discussion.id, merge_request_to_resolve_discussions_of: merge_request.iid)
 
       expect(page).to have_link 'Resolve this discussion in a new issue', href: new_issue_link
     end
 
     context 'creating the issue' do
       before do
-        click_link 'Resolve this discussion in a new issue', href: new_namespace_project_issue_path(project.namespace, project, discussion_to_resolve: discussion.id, merge_request_to_resolve_discussions_of: merge_request.iid)
+        click_link 'Resolve this discussion in a new issue', href: new_project_issue_path(project, discussion_to_resolve: discussion.id, merge_request_to_resolve_discussions_of: merge_request.iid)
       end
 
       it 'has a hidden field for the discussion' do
@@ -66,8 +66,8 @@ feature 'Resolve an open discussion in a merge request by creating an issue', fe
   describe 'as a reporter' do
     before do
       project.team << [user, :reporter]
-      login_as user
-      visit new_namespace_project_issue_path(project.namespace, project,
+      gitlab_sign_in user
+      visit new_project_issue_path(project,
                                              merge_request_to_resolve_discussions_of: merge_request.iid,
                                              discussion_to_resolve: discussion.id)
     end
diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
index 44353d880c2e7ac61304935da86c01d95880b2c6..211f7eec560a7d593e5169e806f26f06bb42b292 100644
--- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
@@ -23,10 +23,10 @@ describe 'Dropdown assignee', :feature, :js do
     project.team << [user, :master]
     project.team << [user_john, :master]
     project.team << [user_jacob, :master]
-    login_as(user)
+    gitlab_sign_in(user)
     create(:issue, project: project)
 
-    visit namespace_project_issues_path(project.namespace, project)
+    visit project_issues_path(project)
   end
 
   describe 'behavior' do
diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb
index 6b707c4be4ae029711c2a648afccaf2b04de4a36..364c5564a1c82edd3f027c930cf7c95e95dd9c23 100644
--- a/spec/features/issues/filtered_search/dropdown_author_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb
@@ -31,10 +31,10 @@ describe 'Dropdown author', js: true, feature: true do
     project.team << [user, :master]
     project.team << [user_john, :master]
     project.team << [user_jacob, :master]
-    login_as(user)
+    gitlab_sign_in(user)
     create(:issue, project: project)
 
-    visit namespace_project_issues_path(project.namespace, project)
+    visit project_issues_path(project)
   end
 
   describe 'behavior' do
diff --git a/spec/features/issues/filtered_search/dropdown_hint_spec.rb b/spec/features/issues/filtered_search/dropdown_hint_spec.rb
index b9a37cfcc2244bd33aa82894feeab01e71401a71..14c506eead3949ca817f6c5f4e318dced7e904df 100644
--- a/spec/features/issues/filtered_search/dropdown_hint_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_hint_spec.rb
@@ -14,10 +14,10 @@ describe 'Dropdown hint', :js, :feature do
 
   before do
     project.team << [user, :master]
-    login_as(user)
+    gitlab_sign_in(user)
     create(:issue, project: project)
 
-    visit namespace_project_issues_path(project.namespace, project)
+    visit project_issues_path(project)
   end
 
   describe 'behavior' do
diff --git a/spec/features/issues/filtered_search/dropdown_label_spec.rb b/spec/features/issues/filtered_search/dropdown_label_spec.rb
index abe5d61e38cad56c328c99de7e527926812bc939..04d2b39dbf2b5a954f20af1008c40c00a18fd8a8 100644
--- a/spec/features/issues/filtered_search/dropdown_label_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_label_spec.rb
@@ -34,10 +34,10 @@ describe 'Dropdown label', js: true, feature: true do
 
   before do
     project.add_master(user)
-    login_as(user)
+    gitlab_sign_in(user)
     create(:issue, project: project)
 
-    visit namespace_project_issues_path(project.namespace, project)
+    visit project_issues_path(project)
   end
 
   describe 'keyboard navigation' do
diff --git a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
index 448259057b012e1f011476412f01a814cd7ee72f..1507e9f76166f00b9b2d81f6887338801e06263c 100644
--- a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
@@ -30,10 +30,10 @@ describe 'Dropdown milestone', :feature, :js do
 
   before do
     project.team << [user, :master]
-    login_as(user)
+    gitlab_sign_in(user)
     create(:issue, project: project)
 
-    visit namespace_project_issues_path(project.namespace, project)
+    visit project_issues_path(project)
   end
 
   describe 'behavior' do
diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb
index 863f8f75cd80fc4cfcff9d34995509df5c453cc6..9fc6391fa98e5e641600397b239c14933c0c326b 100644
--- a/spec/features/issues/filtered_search/filter_issues_spec.rb
+++ b/spec/features/issues/filtered_search/filter_issues_spec.rb
@@ -89,7 +89,7 @@ describe 'Filter issues', js: true, feature: true do
       milestone: future_milestone,
       project: project)
 
-    visit namespace_project_issues_path(project.namespace, project)
+    visit project_issues_path(project)
   end
 
   describe 'filter issues by author' do
@@ -459,7 +459,7 @@ describe 'Filter issues', js: true, feature: true do
 
     context 'issue label clicked' do
       before do
-        find('.issues-list .issue .issue-info a .label', text: multiple_words_label.title).click
+        find('.issues-list .issue .issue-main-info .issuable-info a .label', text: multiple_words_label.title).click
       end
 
       it 'filters' do
@@ -804,7 +804,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)
+      visit project_issues_path(project, milestone_title: milestone.title, assignee_id: user.id)
       link = find_link('Subscribe')
       params = CGI.parse(URI.parse(link[:href]).query)
       auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
@@ -836,7 +836,7 @@ describe 'Filter issues', js: true, feature: true do
 
   context 'URL has a trailing slash' do
     before do
-      visit "#{namespace_project_issues_path(project.namespace, project)}/"
+      visit "#{project_issues_path(project)}/"
     end
 
     it 'milestone dropdown loads milestones' do
diff --git a/spec/features/issues/filtered_search/recent_searches_spec.rb b/spec/features/issues/filtered_search/recent_searches_spec.rb
index 09f228bcf49fb19ada85882dc5028010a2afb0d0..4a91ce4be07599cfe6331ae7d3afd7d7ef164c9d 100644
--- a/spec/features/issues/filtered_search/recent_searches_spec.rb
+++ b/spec/features/issues/filtered_search/recent_searches_spec.rb
@@ -22,7 +22,7 @@ describe 'Recent searches', js: true, feature: true do
   end
 
   it 'searching adds to recent searches' do
-    visit namespace_project_issues_path(project_1.namespace, project_1)
+    visit project_issues_path(project_1)
 
     input_filtered_search('foo', submit: true)
     input_filtered_search('bar', submit: true)
@@ -35,8 +35,8 @@ describe 'Recent searches', js: true, feature: true do
   end
 
   it 'visiting URL with search params adds to recent searches' do
-    visit namespace_project_issues_path(project_1.namespace, project_1, label_name: 'foo', search: 'bar')
-    visit namespace_project_issues_path(project_1.namespace, project_1, label_name: 'qux', search: 'garply')
+    visit project_issues_path(project_1, label_name: 'foo', search: 'bar')
+    visit project_issues_path(project_1, label_name: 'qux', search: 'garply')
 
     items = all('.filtered-search-history-dropdown-item', visible: false)
 
@@ -48,7 +48,7 @@ describe 'Recent searches', js: true, feature: true do
   it 'saved recent searches are restored last on the list' do
     set_recent_searches(project_1_local_storage_key, '["saved1", "saved2"]')
 
-    visit namespace_project_issues_path(project_1.namespace, project_1, search: 'foo')
+    visit project_issues_path(project_1, search: 'foo')
 
     items = all('.filtered-search-history-dropdown-item', visible: false)
 
@@ -59,12 +59,12 @@ describe 'Recent searches', js: true, feature: true do
   end
 
   it 'searches are scoped to projects' do
-    visit namespace_project_issues_path(project_1.namespace, project_1)
+    visit project_issues_path(project_1)
 
     input_filtered_search('foo', submit: true)
     input_filtered_search('bar', submit: true)
 
-    visit namespace_project_issues_path(project_2.namespace, project_2)
+    visit project_issues_path(project_2)
 
     input_filtered_search('more', submit: true)
     input_filtered_search('things', submit: true)
@@ -78,7 +78,7 @@ describe 'Recent searches', js: true, feature: true do
 
   it 'clicking item fills search input' do
     set_recent_searches(project_1_local_storage_key, '["foo", "bar"]')
-    visit namespace_project_issues_path(project_1.namespace, project_1)
+    visit project_issues_path(project_1)
 
     all('.filtered-search-history-dropdown-item', visible: false)[0].trigger('click')
     wait_for_filtered_search('foo')
@@ -88,7 +88,7 @@ describe 'Recent searches', js: true, feature: true do
 
   it 'clear recent searches button, clears recent searches' do
     set_recent_searches(project_1_local_storage_key, '["foo"]')
-    visit namespace_project_issues_path(project_1.namespace, project_1)
+    visit project_issues_path(project_1)
 
     items_before = all('.filtered-search-history-dropdown-item', visible: false)
 
@@ -102,7 +102,7 @@ describe 'Recent searches', js: true, feature: true do
 
   it 'shows flash error when failed to parse saved history' do
     set_recent_searches(project_1_local_storage_key, 'fail')
-    visit namespace_project_issues_path(project_1.namespace, project_1)
+    visit project_issues_path(project_1)
 
     expect(find('.flash-alert')).to have_text('An error occured while parsing recent searches')
   end
diff --git a/spec/features/issues/filtered_search/search_bar_spec.rb b/spec/features/issues/filtered_search/search_bar_spec.rb
index 3ea95aed0a64bb0e7a59ff5559b684fe68de54cb..5b67d062f15eb56e3a50f62038c5e951eb037d36 100644
--- a/spec/features/issues/filtered_search/search_bar_spec.rb
+++ b/spec/features/issues/filtered_search/search_bar_spec.rb
@@ -9,10 +9,10 @@ describe 'Search bar', js: true, feature: true do
 
   before do
     project.team << [user, :master]
-    login_as(user)
+    gitlab_sign_in(user)
     create(:issue, project: project)
 
-    visit namespace_project_issues_path(project.namespace, project)
+    visit project_issues_path(project)
   end
 
   def get_left_style(style)
diff --git a/spec/features/issues/filtered_search/visual_tokens_spec.rb b/spec/features/issues/filtered_search/visual_tokens_spec.rb
index ff32b0c7d11da560de72bfd37baaab27bcaf2d2b..08360bfa641033e454d9f8ca2228963f8503fe9a 100644
--- a/spec/features/issues/filtered_search/visual_tokens_spec.rb
+++ b/spec/features/issues/filtered_search/visual_tokens_spec.rb
@@ -25,10 +25,10 @@ describe 'Visual tokens', js: true, feature: true do
   before do
     project.add_user(user, :master)
     project.add_user(user_rock, :master)
-    login_as(user)
+    gitlab_sign_in(user)
     create(:issue, project: project)
 
-    visit namespace_project_issues_path(project.namespace, project)
+    visit project_issues_path(project)
   end
 
   describe 'editing author token' do
diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb
index 96d37e33f3d268519f166910ac3108c14c0ee06f..5c75b0d56b069456ab046a55908275955243f0f7 100644
--- a/spec/features/issues/form_spec.rb
+++ b/spec/features/issues/form_spec.rb
@@ -1,7 +1,6 @@
 require 'rails_helper'
 
 describe 'New/edit issue', :feature, :js do
-  include GitlabRoutingHelper
   include ActionView::Helpers::JavaScriptHelper
   include FormHelper
 
@@ -16,12 +15,12 @@ describe 'New/edit issue', :feature, :js do
   before do
     project.team << [user, :master]
     project.team << [user2, :master]
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   context 'new issue' do
     before do
-      visit new_namespace_project_issue_path(project.namespace, project)
+      visit new_project_issue_path(project)
     end
 
     describe 'shorten users API pagination limit (CE)' do
@@ -31,15 +30,15 @@ describe 'New/edit issue', :feature, :js do
         # the original method, resulting in infinite recurison when called.
         # This is likely a bug with helper modules included into dynamically generated view classes.
         # To work around this, we have to hold on to and call to the original implementation manually.
-        original_issue_dropdown_options = FormHelper.instance_method(:issue_dropdown_options)
-        allow_any_instance_of(FormHelper).to receive(:issue_dropdown_options).and_wrap_original do |original, *args|
+        original_issue_dropdown_options = FormHelper.instance_method(:issue_assignees_dropdown_options)
+        allow_any_instance_of(FormHelper).to receive(:issue_assignees_dropdown_options).and_wrap_original do |original, *args|
           options = original_issue_dropdown_options.bind(original.receiver).call(*args)
           options[:data][:per_page] = 2
 
           options
         end
 
-        visit new_namespace_project_issue_path(project.namespace, project)
+        visit new_project_issue_path(project)
 
         click_button 'Unassigned'
 
@@ -210,11 +209,18 @@ describe 'New/edit issue', :feature, :js do
 
       expect(find('.js-assignee-search')).to have_content(user2.name)
     end
+
+    it 'description has autocomplete' do
+      find('#issue_description').native.send_keys('')
+      fill_in 'issue_description', with: '@'
+
+      expect(page).to have_selector('.atwho-view')
+    end
   end
 
   context 'edit issue' do
     before do
-      visit edit_namespace_project_issue_path(project.namespace, project, issue)
+      visit edit_project_issue_path(project, issue)
     end
 
     it 'allows user to update issue' do
@@ -258,6 +264,13 @@ describe 'New/edit issue', :feature, :js do
         end
       end
     end
+
+    it 'description has autocomplete' do
+      find('#issue_description').native.send_keys('')
+      fill_in 'issue_description', with: '@'
+
+      expect(page).to have_selector('.atwho-view')
+    end
   end
 
   describe 'sub-group project' do
@@ -268,7 +281,7 @@ describe 'New/edit issue', :feature, :js do
     before do
       sub_group_project.add_master(user)
 
-      visit new_namespace_project_issue_path(sub_group_project.namespace, sub_group_project)
+      visit new_project_issue_path(sub_group_project)
     end
 
     it 'creates new label from dropdown' do
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
index 350473437a811bb26e2929db5ee4e2da8c8f0c81..a0f26bf9a92c57edc3d63ae0e64a055bd9f25efa 100644
--- a/spec/features/issues/gfm_autocomplete_spec.rb
+++ b/spec/features/issues/gfm_autocomplete_spec.rb
@@ -8,8 +8,8 @@ feature 'GFM autocomplete', feature: true, js: true do
 
   before do
     project.team << [user, :master]
-    login_as(user)
-    visit namespace_project_issue_path(project.namespace, project, issue)
+    gitlab_sign_in(user)
+    visit project_issue_path(project, issue)
 
     wait_for_requests
   end
@@ -208,7 +208,7 @@ feature 'GFM autocomplete', feature: true, js: true do
       expect(page).not_to have_selector('.atwho-view')
     end
 
-    it 'triggers autocomplete after selecting a slash command' do
+    it 'triggers autocomplete after selecting a quick action' do
       note = find('#note_note')
       page.within '.timeline-content-form' do
         note.native.send_keys('')
diff --git a/spec/features/issues/group_label_sidebar_spec.rb b/spec/features/issues/group_label_sidebar_spec.rb
index fc8515cfe9bc24092afb2467696744db3d402c3f..5531a662c67e2a3df53078c2663fd4e522c59332 100644
--- a/spec/features/issues/group_label_sidebar_spec.rb
+++ b/spec/features/issues/group_label_sidebar_spec.rb
@@ -6,13 +6,9 @@ describe 'Group label on issue', :feature do
     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]
-    )
+    label_link = project_issues_path(project, label_name: [feature.name])
 
-    visit namespace_project_issue_path(project.namespace, project, issue)
+    visit project_issue_path(project, issue)
 
     link = find('.issuable-show-labels a')
 
diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb
index 96c24750250a7036a2709e8af80eef20c6ece7fe..8d9bfcdf4e0ce10ec38686da68bf35d38e3830da 100644
--- a/spec/features/issues/issue_sidebar_spec.rb
+++ b/spec/features/issues/issue_sidebar_spec.rb
@@ -10,7 +10,7 @@ feature 'Issue Sidebar', feature: true do
   let!(:label) { create(:label, project: project, title: 'bug') }
 
   before do
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   context 'assignee', js: true do
@@ -154,20 +154,6 @@ feature 'Issue Sidebar', feature: true do
     end
   end
 
-  context 'as a allowed mobile user', js: true do
-    before do
-      project.team << [user, :developer]
-      resize_screen_xs
-      visit_issue(project, issue)
-    end
-
-    context 'mobile sidebar' do
-      it 'collapses the sidebar for small screens' do
-        expect(page).not_to have_css('aside.right-sidebar.right-sidebar-collapsed')
-      end
-    end
-  end
-
   context 'as a guest' do
     before do
       project.team << [user, :guest]
@@ -180,7 +166,7 @@ feature 'Issue Sidebar', feature: true do
   end
 
   def visit_issue(project, issue)
-    visit namespace_project_issue_path(project.namespace, project, issue)
+    visit project_issue_path(project, issue)
   end
 
   def open_issue_sidebar
diff --git a/spec/features/issues/markdown_toolbar_spec.rb b/spec/features/issues/markdown_toolbar_spec.rb
index c8c9c50396bee009cac0cff4f60023488a7692bb..396b53556bf2cea13fb98188cd610c6dee32a28f 100644
--- a/spec/features/issues/markdown_toolbar_spec.rb
+++ b/spec/features/issues/markdown_toolbar_spec.rb
@@ -6,9 +6,9 @@ feature 'Issue markdown toolbar', feature: true, js: true do
   let(:user)   { create(:user) }
 
   before do
-    login_as(user)
+    gitlab_sign_in(user)
 
-    visit namespace_project_issue_path(project.namespace, project, issue)
+    visit project_issue_path(project, issue)
   end
 
   it "doesn't include first new line when adding bold" do
diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb
index e75bf0592189980b5a90ea1f05b0817ba4a69569..568f2393aef98e26898ca67cc23bdb0e1b8fc6cc 100644
--- a/spec/features/issues/move_spec.rb
+++ b/spec/features/issues/move_spec.rb
@@ -9,7 +9,7 @@ feature 'issue move to another project' do
     create(:issue, description: text, project: old_project, author: user)
   end
 
-  background { login_as(user) }
+  background { gitlab_sign_in(user) }
 
   context 'user does not have permission to move issue' do
     background do
@@ -41,13 +41,10 @@ feature 'issue move to another project' do
       find('#issuable-move', visible: false).set(new_project.id)
       click_button('Save changes')
 
-      wait_for_requests
-
-      expect(current_url).to include project_path(new_project)
-
       expect(page).to have_content("Text with #{cross_reference}#{mr.to_reference}")
       expect(page).to have_content("moved from #{cross_reference}#{issue.to_reference}")
       expect(page).to have_content(issue.title)
+      expect(page.current_path).to include project_path(new_project)
     end
 
     scenario 'searching project dropdown', js: true do
@@ -98,10 +95,6 @@ feature 'issue move to another project' do
   end
 
   def issue_path(issue)
-    namespace_project_issue_path(issue.project.namespace, issue.project, issue)
-  end
-
-  def project_path(project)
-    namespace_project_path(new_project.namespace, new_project)
+    project_issue_path(issue.project, issue)
   end
 end
diff --git a/spec/features/issues/note_polling_spec.rb b/spec/features/issues/note_polling_spec.rb
index 2c0a6ffd3cbfdf91c7f5c2b93934f5bc759ddabe..580b8d03fef65bb75e4c2c3507b119b8c23c9888 100644
--- a/spec/features/issues/note_polling_spec.rb
+++ b/spec/features/issues/note_polling_spec.rb
@@ -8,7 +8,7 @@ feature 'Issue notes polling', :feature, :js do
 
   describe 'creates' do
     before do
-      visit namespace_project_issue_path(project.namespace, project, issue)
+      visit project_issue_path(project, issue)
     end
 
     it 'displays the new comment' do
@@ -27,8 +27,8 @@ feature 'Issue notes polling', :feature, :js do
       let!(:existing_note) { create(:note, noteable: issue, project: project, author: user, note: note_text) }
 
       before do
-        login_as(user)
-        visit namespace_project_issue_path(project.namespace, project, issue)
+        gitlab_sign_in(user)
+        visit project_issue_path(project, issue)
       end
 
       it 'has .original-note-content to compare against' do
@@ -93,8 +93,8 @@ feature 'Issue notes polling', :feature, :js do
       let!(:existing_note) { create(:note, noteable: issue, project: project, author: user1, note: note_text) }
 
       before do
-        login_as(user2)
-        visit namespace_project_issue_path(project.namespace, project, issue)
+        gitlab_sign_in(user2)
+        visit project_issue_path(project, issue)
       end
 
       it 'has .original-note-content to compare against' do
@@ -114,8 +114,8 @@ feature 'Issue notes polling', :feature, :js do
       let!(:system_note) { create(:system_note, noteable: issue, project: project, author: user, note: note_text) }
 
       before do
-        login_as(user)
-        visit namespace_project_issue_path(project.namespace, project, issue)
+        gitlab_sign_in(user)
+        visit project_issue_path(project, issue)
       end
 
       it 'has .original-note-content to compare against' do
diff --git a/spec/features/issues/notes_on_issues_spec.rb b/spec/features/issues/notes_on_issues_spec.rb
index 15c817cabac901ad6c1802bf1169ee27166954d2..1871d853a9019afe0cd3e414fbd9c3ca1fd95bf1 100644
--- a/spec/features/issues/notes_on_issues_spec.rb
+++ b/spec/features/issues/notes_on_issues_spec.rb
@@ -9,8 +9,8 @@ describe 'Create notes on issues', :js, :feature do
 
     before do
       project.team << [user, :developer]
-      login_as(user)
-      visit namespace_project_issue_path(project.namespace, project, issue)
+      gitlab_sign_in(user)
+      visit project_issue_path(project, issue)
 
       fill_in 'note[note]', with: note_text
       click_button 'Comment'
diff --git a/spec/features/issues/spam_issues_spec.rb b/spec/features/issues/spam_issues_spec.rb
index 6001476d0ca676cf489514796f80a0d383ef1155..76dae9212ddd8b58a8979f81bcd6925a532c108c 100644
--- a/spec/features/issues/spam_issues_spec.rb
+++ b/spec/features/issues/spam_issues_spec.rb
@@ -18,14 +18,14 @@ describe 'New issue', feature: true, js: true do
     )
 
     project.team << [user, :master]
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   context 'when identified as a spam' do
     before do
       WebMock.stub_request(:any, /.*akismet.com.*/).to_return(body: "true", status: 200)
 
-      visit new_namespace_project_issue_path(project.namespace, project)
+      visit new_project_issue_path(project)
     end
 
     it 'creates an issue after solving reCaptcha' do
@@ -50,7 +50,7 @@ describe 'New issue', feature: true, js: true do
     before do
       WebMock.stub_request(:any, /.*akismet.com.*/).to_return(body: 'false', status: 200)
 
-      visit new_namespace_project_issue_path(project.namespace, project)
+      visit new_project_issue_path(project)
     end
 
     it 'creates an issue' do
diff --git a/spec/features/issues/todo_spec.rb b/spec/features/issues/todo_spec.rb
index 3fde85b0a5c5178887677d988ac67167d183352d..1bcd717e8ddf633e07d10bdaadaf5094f08cec75 100644
--- a/spec/features/issues/todo_spec.rb
+++ b/spec/features/issues/todo_spec.rb
@@ -7,8 +7,8 @@ feature 'Manually create a todo item from issue', feature: true, js: true do
 
   before do
     project.team << [user, :master]
-    login_as(user)
-    visit namespace_project_issue_path(project.namespace, project, issue)
+    gitlab_sign_in(user)
+    visit project_issue_path(project, issue)
   end
 
   it 'creates todo when clicking button' do
@@ -21,7 +21,7 @@ feature 'Manually create a todo item from issue', feature: true, js: true do
       expect(page).to have_content '1'
     end
 
-    visit namespace_project_issue_path(project.namespace, project, issue)
+    visit project_issue_path(project, issue)
 
     page.within '.header-content .todos-count' do
       expect(page).to have_content '1'
@@ -36,7 +36,7 @@ feature 'Manually create a todo item from issue', feature: true, js: true do
 
     expect(page).to have_selector('.todos-count', visible: false)
 
-    visit namespace_project_issue_path(project.namespace, project, issue)
+    visit project_issue_path(project, issue)
 
     expect(page).to have_selector('.todos-count', visible: false)
   end
diff --git a/spec/features/issues/update_issues_spec.rb b/spec/features/issues/update_issues_spec.rb
index 8595847d31356407f9bd536c44e7cf54f6aa347d..5a7c4f54cb638cf1d604a41ed1f92bfed1a53758 100644
--- a/spec/features/issues/update_issues_spec.rb
+++ b/spec/features/issues/update_issues_spec.rb
@@ -1,18 +1,18 @@
 require 'rails_helper'
 
-feature 'Multiple issue updating from issues#index', feature: true do
+feature 'Multiple issue updating from issues#index', :js do
   let!(:project)   { create(:project) }
   let!(:issue)     { create(:issue, project: project) }
   let!(:user)      { create(:user)}
 
   before do
     project.team << [user, :master]
-    login_as(user)
+    sign_in(user)
   end
 
-  context 'status', js: true do
+  context 'status' do
     it 'sets to closed' do
-      visit namespace_project_issues_path(project.namespace, project)
+      visit project_issues_path(project)
 
       click_button 'Edit Issues'
       find('#check-all-issues').click
@@ -25,7 +25,7 @@ feature 'Multiple issue updating from issues#index', feature: true do
 
     it 'sets to open' do
       create_closed
-      visit namespace_project_issues_path(project.namespace, project, state: 'closed')
+      visit project_issues_path(project, state: 'closed')
 
       click_button 'Edit Issues'
       find('#check-all-issues').click
@@ -37,9 +37,9 @@ feature 'Multiple issue updating from issues#index', feature: true do
     end
   end
 
-  context 'assignee', js: true do
+  context 'assignee' do
     it 'updates to current user' do
-      visit namespace_project_issues_path(project.namespace, project)
+      visit project_issues_path(project)
 
       click_button 'Edit Issues'
       find('#check-all-issues').click
@@ -55,7 +55,7 @@ feature 'Multiple issue updating from issues#index', feature: true do
 
     it 'updates to unassigned' do
       create_assigned
-      visit namespace_project_issues_path(project.namespace, project)
+      visit project_issues_path(project)
 
       click_button 'Edit Issues'
       find('#check-all-issues').click
@@ -67,11 +67,11 @@ feature 'Multiple issue updating from issues#index', feature: true do
     end
   end
 
-  context 'milestone', js: true do
-    let(:milestone)  { create(:milestone, project: project) }
+  context 'milestone' do
+    let!(:milestone) { create(:milestone, project: project) }
 
     it 'updates milestone' do
-      visit namespace_project_issues_path(project.namespace, project)
+      visit project_issues_path(project)
 
       click_button 'Edit Issues'
       find('#check-all-issues').click
@@ -85,7 +85,7 @@ feature 'Multiple issue updating from issues#index', feature: true do
 
     it 'sets to no milestone' do
       create_with_milestone
-      visit namespace_project_issues_path(project.namespace, project)
+      visit project_issues_path(project)
 
       expect(first('.issue')).to have_content milestone.title
 
diff --git a/spec/features/issues/user_uses_slash_commands_spec.rb b/spec/features/issues/user_uses_slash_commands_spec.rb
index d14c319707c76685497da40abad464ef803bfee5..ad28decfc007d31cb0a51e3c96e05fbe247b7d6f 100644
--- a/spec/features/issues/user_uses_slash_commands_spec.rb
+++ b/spec/features/issues/user_uses_slash_commands_spec.rb
@@ -1,9 +1,9 @@
 require 'rails_helper'
 
-feature 'Issues > User uses slash commands', feature: true, js: true do
-  include SlashCommandsHelpers
+feature 'Issues > User uses quick actions', feature: true, js: true do
+  include QuickActionsHelpers
 
-  it_behaves_like 'issuable record that supports slash commands in its description and notes', :issue do
+  it_behaves_like 'issuable record that supports quick actions in its description and notes', :issue do
     let(:issuable) { create(:issue, project: project) }
   end
 
@@ -13,8 +13,8 @@ feature 'Issues > User uses slash commands', feature: true, js: true do
 
     before do
       project.team << [user, :master]
-      login_with(user)
-      visit namespace_project_issue_path(project.namespace, project, issue)
+      gitlab_sign_in(user)
+      visit project_issue_path(project, issue)
     end
 
     after do
@@ -41,9 +41,9 @@ feature 'Issues > User uses slash commands', feature: true, js: true do
         let(:guest) { create(:user) }
         before do
           project.team << [guest, :guest]
-          logout
-          login_with(guest)
-          visit namespace_project_issue_path(project.namespace, project, issue)
+          gitlab_sign_out
+          gitlab_sign_in(guest)
+          visit project_issue_path(project, issue)
         end
 
         it 'does not create a note, and sets the due date accordingly' do
@@ -81,9 +81,9 @@ feature 'Issues > User uses slash commands', feature: true, js: true do
         let(:guest) { create(:user) }
         before do
           project.team << [guest, :guest]
-          logout
-          login_with(guest)
-          visit namespace_project_issue_path(project.namespace, project, issue)
+          gitlab_sign_out
+          gitlab_sign_in(guest)
+          visit project_issue_path(project, issue)
         end
 
         it 'does not create a note, and sets the due date accordingly' do
@@ -108,7 +108,7 @@ feature 'Issues > User uses slash commands', feature: true, js: true do
 
       context 'Issue' do
         before do
-          visit namespace_project_issue_path(project.namespace, project, issue)
+          visit project_issue_path(project, issue)
         end
 
         it_behaves_like 'issuable time tracker'
@@ -118,7 +118,7 @@ feature 'Issues > User uses slash commands', feature: true, js: true do
         let(:merge_request) { create(:merge_request, source_project: project) }
 
         before do
-          visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+          visit project_merge_request_path(project, merge_request)
         end
 
         it_behaves_like 'issuable time tracker'
@@ -134,7 +134,7 @@ feature 'Issues > User uses slash commands', feature: true, js: true do
 
       context 'Issue' do
         before do
-          visit namespace_project_issue_path(project.namespace, project, issue)
+          visit project_issue_path(project, issue)
         end
 
         it_behaves_like 'issuable time tracker'
@@ -144,7 +144,7 @@ feature 'Issues > User uses slash commands', feature: true, js: true do
         let(:merge_request) { create(:merge_request, source_project: project) }
 
         before do
-          visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+          visit project_merge_request_path(project, merge_request)
         end
 
         it_behaves_like 'issuable time tracker'
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index 2cff53539f39b11af7d2850ff971dcb9aae91a5e..8cb62910e18d821598dcf0fe7aa09366c2fa70a4 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -5,25 +5,26 @@ describe 'Issues', feature: true do
   include IssueHelpers
   include SortingHelper
 
+  let(:user) { create(:user) }
   let(:project) { create(:empty_project, :public) }
 
   before do
-    login_as :user
+    sign_in(user)
     user2 = create(:user)
 
-    project.team << [[@user, user2], :developer]
+    project.team << [[user, user2], :developer]
   end
 
   describe 'Edit issue' do
     let!(:issue) do
       create(:issue,
-             author: @user,
-             assignees: [@user],
+             author: user,
+             assignees: [user],
              project: project)
     end
 
     before do
-      visit edit_namespace_project_issue_path(project.namespace, project, issue)
+      visit edit_project_issue_path(project, issue)
       find('.js-zen-enter').click
     end
 
@@ -35,15 +36,15 @@ describe 'Issues', feature: true do
   describe 'Editing issue assignee' do
     let!(:issue) do
       create(:issue,
-             author: @user,
-             assignees: [@user],
+             author: user,
+             assignees: [user],
              project: project)
     end
 
     it 'allows user to select unassigned', js: true do
-      visit edit_namespace_project_issue_path(project.namespace, project, issue)
+      visit edit_project_issue_path(project, issue)
 
-      expect(page).to have_content "Assignee #{@user.name}"
+      expect(page).to have_content "Assignee #{user.name}"
 
       first('.js-user-search').click
       click_link 'Unassigned'
@@ -61,7 +62,7 @@ describe 'Issues', feature: true do
   describe 'due date', js: true do
     context 'on new form' do
       before do
-        visit new_namespace_project_issue_path(project.namespace, project)
+        visit new_project_issue_path(project)
       end
 
       it 'saves with due date' do
@@ -86,10 +87,10 @@ describe 'Issues', feature: true do
     end
 
     context 'on edit form' do
-      let(:issue) { create(:issue, author: @user, project: project, due_date: Date.today.at_beginning_of_month.to_s) }
+      let(:issue) { create(:issue, author: user, project: project, due_date: Date.today.at_beginning_of_month.to_s) }
 
       before do
-        visit edit_namespace_project_issue_path(project.namespace, project, issue)
+        visit edit_project_issue_path(project, issue)
       end
 
       it 'saves with due date' do
@@ -131,10 +132,10 @@ describe 'Issues', feature: true do
 
   describe 'Issue info' do
     it 'excludes award_emoji from comment count' do
-      issue = create(:issue, author: @user, assignees: [@user], project: project, title: 'foobar')
+      issue = create(:issue, author: user, assignees: [user], project: project, title: 'foobar')
       create(:award_emoji, awardable: issue)
 
-      visit namespace_project_issues_path(project.namespace, project, assignee_id: @user.id)
+      visit project_issues_path(project, assignee_id: user.id)
 
       expect(page).to have_content 'foobar'
       expect(page.all('.no-comments').first.text).to eq "0"
@@ -145,8 +146,8 @@ describe 'Issues', feature: true do
     before do
       %w(foobar barbaz gitlab).each do |title|
         create(:issue,
-               author: @user,
-               assignees: [@user],
+               author: user,
+               assignees: [user],
                project: project,
                title: title)
       end
@@ -160,7 +161,7 @@ describe 'Issues', feature: true do
     let(:issue) { @issue }
 
     it 'allows filtering by issues with no specified assignee' do
-      visit namespace_project_issues_path(project.namespace, project, assignee_id: IssuableFinder::NONE)
+      visit project_issues_path(project, assignee_id: IssuableFinder::NONE)
 
       expect(page).to have_content 'foobar'
       expect(page).not_to have_content 'barbaz'
@@ -168,7 +169,7 @@ describe 'Issues', feature: true do
     end
 
     it 'allows filtering by a specified assignee' do
-      visit namespace_project_issues_path(project.namespace, project, assignee_id: @user.id)
+      visit project_issues_path(project, assignee_id: user.id)
 
       expect(page).not_to have_content 'foobar'
       expect(page).to have_content 'barbaz'
@@ -189,14 +190,14 @@ describe 'Issues', feature: true do
     let(:later_due_milestone) { create(:milestone, due_date: '2013-12-12') }
 
     it 'sorts by newest' do
-      visit namespace_project_issues_path(project.namespace, project, sort: sort_value_recently_created)
+      visit project_issues_path(project, sort: sort_value_recently_created)
 
       expect(first_issue).to include('foo')
       expect(last_issue).to include('baz')
     end
 
     it 'sorts by oldest' do
-      visit namespace_project_issues_path(project.namespace, project, sort: sort_value_oldest_created)
+      visit project_issues_path(project, sort: sort_value_oldest_created)
 
       expect(first_issue).to include('baz')
       expect(last_issue).to include('foo')
@@ -205,7 +206,7 @@ describe 'Issues', feature: true do
     it 'sorts by most recently updated' do
       baz.updated_at = Time.now + 100
       baz.save
-      visit namespace_project_issues_path(project.namespace, project, sort: sort_value_recently_updated)
+      visit project_issues_path(project, sort: sort_value_recently_updated)
 
       expect(first_issue).to include('baz')
     end
@@ -213,7 +214,7 @@ describe 'Issues', feature: true do
     it 'sorts by least recently updated' do
       baz.updated_at = Time.now - 100
       baz.save
-      visit namespace_project_issues_path(project.namespace, project, sort: sort_value_oldest_updated)
+      visit project_issues_path(project, sort: sort_value_oldest_updated)
 
       expect(first_issue).to include('baz')
     end
@@ -225,13 +226,13 @@ describe 'Issues', feature: true do
       end
 
       it 'sorts by recently due date' do
-        visit namespace_project_issues_path(project.namespace, project, sort: sort_value_due_date_soon)
+        visit project_issues_path(project, sort: sort_value_due_date_soon)
 
         expect(first_issue).to include('foo')
       end
 
       it 'sorts by least recently due date' do
-        visit namespace_project_issues_path(project.namespace, project, sort: sort_value_due_date_later)
+        visit project_issues_path(project, sort: sort_value_due_date_later)
 
         expect(first_issue).to include('bar')
       end
@@ -239,7 +240,7 @@ describe 'Issues', feature: true do
       it 'sorts by least recently due date by excluding nil due dates' do
         bar.update(due_date: nil)
 
-        visit namespace_project_issues_path(project.namespace, project, sort: sort_value_due_date_later)
+        visit project_issues_path(project, sort: sort_value_due_date_later)
 
         expect(first_issue).to include('foo')
       end
@@ -254,7 +255,7 @@ describe 'Issues', feature: true do
         it 'sorts by least recently due date by excluding nil due dates' do
           bar.update(due_date: nil)
 
-          visit namespace_project_issues_path(project.namespace, project, label_names: [label.name], sort: sort_value_due_date_later)
+          visit project_issues_path(project, label_names: [label.name], sort: sort_value_due_date_later)
 
           expect(first_issue).to include('foo')
         end
@@ -268,7 +269,7 @@ describe 'Issues', feature: true do
       end
 
       it 'filters by none' do
-        visit namespace_project_issues_path(project.namespace, project, due_date: Issue::NoDueDate.name)
+        visit project_issues_path(project, due_date: Issue::NoDueDate.name)
 
         expect(page).not_to have_content('foo')
         expect(page).not_to have_content('bar')
@@ -276,7 +277,7 @@ describe 'Issues', feature: true do
       end
 
       it 'filters by any' do
-        visit namespace_project_issues_path(project.namespace, project, due_date: Issue::AnyDueDate.name)
+        visit project_issues_path(project, due_date: Issue::AnyDueDate.name)
 
         expect(page).to have_content('foo')
         expect(page).to have_content('bar')
@@ -288,7 +289,7 @@ describe 'Issues', feature: true do
         bar.update(due_date: Date.today.end_of_week)
         baz.update(due_date: Date.today - 8.days)
 
-        visit namespace_project_issues_path(project.namespace, project, due_date: Issue::DueThisWeek.name)
+        visit project_issues_path(project, due_date: Issue::DueThisWeek.name)
 
         expect(page).to have_content('foo')
         expect(page).to have_content('bar')
@@ -300,7 +301,7 @@ describe 'Issues', feature: true do
         bar.update(due_date: Date.today.end_of_month)
         baz.update(due_date: Date.today - 50.days)
 
-        visit namespace_project_issues_path(project.namespace, project, due_date: Issue::DueThisMonth.name)
+        visit project_issues_path(project, due_date: Issue::DueThisMonth.name)
 
         expect(page).to have_content('foo')
         expect(page).to have_content('bar')
@@ -312,7 +313,7 @@ describe 'Issues', feature: true do
         bar.update(due_date: Date.today + 20.days)
         baz.update(due_date: Date.yesterday)
 
-        visit namespace_project_issues_path(project.namespace, project, due_date: Issue::Overdue.name)
+        visit project_issues_path(project, due_date: Issue::Overdue.name)
 
         expect(page).not_to have_content('foo')
         expect(page).not_to have_content('bar')
@@ -329,14 +330,14 @@ describe 'Issues', feature: true do
       end
 
       it 'sorts by recently due milestone' do
-        visit namespace_project_issues_path(project.namespace, project, sort: sort_value_milestone_soon)
+        visit project_issues_path(project, sort: sort_value_milestone_soon)
 
         expect(first_issue).to include('foo')
         expect(last_issue).to include('baz')
       end
 
       it 'sorts by least recently due milestone' do
-        visit namespace_project_issues_path(project.namespace, project, sort: sort_value_milestone_later)
+        visit project_issues_path(project, sort: sort_value_milestone_later)
 
         expect(first_issue).to include('bar')
         expect(last_issue).to include('baz')
@@ -354,7 +355,7 @@ describe 'Issues', feature: true do
       end
 
       it 'sorts with a filter applied' do
-        visit namespace_project_issues_path(project.namespace, project,
+        visit project_issues_path(project,
                                             sort: sort_value_oldest_created,
                                             assignee_id: user2.id)
 
@@ -366,13 +367,13 @@ describe 'Issues', feature: true do
   end
 
   describe 'when I want to reset my incoming email token' do
-    let(:project1) { create(:empty_project, namespace: @user.namespace) }
+    let(:project1) { create(:empty_project, namespace: user.namespace) }
     let!(:issue) { create(:issue, project: project1) }
 
     before do
       stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab")
-      project1.team << [@user, :master]
-      visit namespace_project_issues_path(@user.namespace, project1)
+      project1.team << [user, :master]
+      visit namespace_project_issues_path(user.namespace, project1)
     end
 
     it 'changes incoming email address token', js: true do
@@ -383,7 +384,7 @@ describe 'Issues', feature: true do
       wait_for_requests
 
       expect(page).to have_no_field('issue_email', with: previous_token)
-      new_token = project1.new_issue_address(@user.reload)
+      new_token = project1.new_issue_address(user.reload)
       expect(page).to have_field(
         'issue_email',
         with: new_token
@@ -392,11 +393,11 @@ describe 'Issues', feature: true do
   end
 
   describe 'update labels from issue#show', js: true do
-    let(:issue) { create(:issue, project: project, author: @user, assignees: [@user]) }
+    let(:issue) { create(:issue, project: project, author: user, assignees: [user]) }
     let!(:label) { create(:label, project: project) }
 
     before do
-      visit namespace_project_issue_path(project.namespace, project, issue)
+      visit project_issue_path(project, issue)
     end
 
     it 'will not send ajax request when no data is changed' do
@@ -411,14 +412,14 @@ describe 'Issues', feature: true do
   end
 
   describe 'update assignee from issue#show' do
-    let(:issue) { create(:issue, project: project, author: @user, assignees: [@user]) }
+    let(:issue) { create(:issue, project: project, author: user, assignees: [user]) }
 
     context 'by authorized user' do
       it 'allows user to select unassigned', js: true do
-        visit namespace_project_issue_path(project.namespace, project, issue)
+        visit project_issue_path(project, issue)
 
         page.within('.assignee') do
-          expect(page).to have_content "#{@user.name}"
+          expect(page).to have_content "#{user.name}"
 
           click_link 'Edit'
           click_link 'Unassigned'
@@ -433,8 +434,8 @@ describe 'Issues', feature: true do
       end
 
       it 'allows user to select an assignee', js: true do
-        issue2 = create(:issue, project: project, author: @user)
-        visit namespace_project_issue_path(project.namespace, project, issue2)
+        issue2 = create(:issue, project: project, author: user)
+        visit project_issue_path(project, issue2)
 
         page.within('.assignee') do
           expect(page).to have_content "No assignee"
@@ -445,28 +446,28 @@ describe 'Issues', feature: true do
         end
 
         page.within '.dropdown-menu-user' do
-          click_link @user.name
+          click_link user.name
         end
 
         page.within('.assignee') do
-          expect(page).to have_content @user.name
+          expect(page).to have_content user.name
         end
       end
 
       it 'allows user to unselect themselves', js: true do
-        issue2 = create(:issue, project: project, author: @user)
-        visit namespace_project_issue_path(project.namespace, project, issue2)
+        issue2 = create(:issue, project: project, author: user)
+        visit project_issue_path(project, issue2)
 
         page.within '.assignee' do
           click_link 'Edit'
-          click_link @user.name
+          click_link user.name
 
           page.within '.value .author' do
-            expect(page).to have_content @user.name
+            expect(page).to have_content user.name
           end
 
           click_link 'Edit'
-          click_link @user.name
+          click_link user.name
 
           page.within '.value .assign-yourself' do
             expect(page).to have_content "No assignee"
@@ -483,22 +484,22 @@ describe 'Issues', feature: true do
       end
 
       it 'shows assignee text', js: true do
-        logout
-        login_with guest
+        sign_out(:user)
+        sign_in(guest)
 
-        visit namespace_project_issue_path(project.namespace, project, issue)
+        visit project_issue_path(project, issue)
         expect(page).to have_content issue.assignees.first.name
       end
     end
   end
 
   describe 'update milestone from issue#show' do
-    let!(:issue) { create(:issue, project: project, author: @user) }
+    let!(:issue) { create(:issue, project: project, author: user) }
     let!(:milestone) { create(:milestone, project: project) }
 
     context 'by authorized user' do
       it 'allows user to select unassigned', js: true do
-        visit namespace_project_issue_path(project.namespace, project, issue)
+        visit project_issue_path(project, issue)
 
         page.within('.milestone') do
           expect(page).to have_content "None"
@@ -516,7 +517,7 @@ describe 'Issues', feature: true do
       end
 
       it 'allows user to de-select milestone', js: true do
-        visit namespace_project_issue_path(project.namespace, project, issue)
+        visit project_issue_path(project, issue)
 
         page.within('.milestone') do
           click_link 'Edit'
@@ -546,10 +547,10 @@ describe 'Issues', feature: true do
       end
 
       it 'shows milestone text', js: true do
-        logout
-        login_with guest
+        sign_out(:user)
+        sign_in(guest)
 
-        visit namespace_project_issue_path(project.namespace, project, issue)
+        visit project_issue_path(project, issue)
         expect(page).to have_content milestone.title
       end
     end
@@ -560,25 +561,27 @@ describe 'Issues', feature: true do
 
     context 'by unauthenticated user' do
       before do
-        logout
+        sign_out(:user)
       end
 
       it 'redirects to signin then back to new issue after signin' do
-        visit namespace_project_issues_path(project.namespace, project)
+        visit project_issues_path(project)
 
         click_link 'New issue'
 
         expect(current_path).to eq new_user_session_path
 
-        login_as :user
+        # NOTE: This is specifically testing the redirect after login, so we
+        # need the full login flow
+        gitlab_sign_in(create(:user))
 
-        expect(current_path).to eq new_namespace_project_issue_path(project.namespace, project)
+        expect(current_path).to eq new_project_issue_path(project)
       end
     end
 
     context 'dropzone upload file', js: true do
       before do
-        visit new_namespace_project_issue_path(project.namespace, project)
+        visit new_project_issue_path(project)
       end
 
       it 'uploads file when dragging into textarea' do
@@ -599,13 +602,13 @@ describe 'Issues', feature: true do
 
       before do
         project.repository.create_file(
-          @user,
+          user,
           '.gitlab/issue_templates/bug.md',
           'this is a test "bug" template',
           message: 'added issue template',
           branch_name: 'master')
 
-        visit new_namespace_project_issue_path(project.namespace, project, issuable_template: 'bug')
+        visit new_project_issue_path(project, issuable_template: 'bug')
       end
 
       it 'fills in template' do
@@ -622,13 +625,13 @@ describe 'Issues', feature: true do
         project.issues << issue
         stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab")
 
-        visit namespace_project_issues_path(project.namespace, project)
+        visit project_issues_path(project)
         click_button('Email a new issue')
       end
 
       it 'click the button to show modal for the new email' do
         page.within '#issue-email-modal' do
-          email = project.new_issue_address(@user)
+          email = project.new_issue_address(user)
 
           expect(page).to have_selector("input[value='#{email}']")
         end
@@ -636,7 +639,7 @@ describe 'Issues', feature: true do
     end
 
     context 'with existing issues' do
-      let!(:issue) { create(:issue, project: project, author: @user) }
+      let!(:issue) { create(:issue, project: project, author: user) }
 
       it_behaves_like 'show the email in the modal'
     end
@@ -648,10 +651,10 @@ describe 'Issues', feature: true do
 
   describe 'due date' do
     context 'update due on issue#show', js: true do
-      let(:issue) { create(:issue, project: project, author: @user, assignees: [@user]) }
+      let(:issue) { create(:issue, project: project, author: user, assignees: [user]) }
 
       before do
-        visit namespace_project_issue_path(project.namespace, project, issue)
+        visit project_issue_path(project, issue)
       end
 
       it 'adds due date to issue' do
@@ -693,9 +696,9 @@ describe 'Issues', feature: true do
 
   describe 'title issue#show', js: true do
     it 'updates the title', js: true do
-      issue = create(:issue, author: @user, assignees: [@user], project: project, title: 'new title')
+      issue = create(:issue, author: user, assignees: [user], project: project, title: 'new title')
 
-      visit namespace_project_issue_path(project.namespace, project, issue)
+      visit project_issue_path(project, issue)
 
       expect(page).to have_text("new title")
 
diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb
index 4763f45481064f260917096c743b340491037600..a8055b21cee158338ca6ee0b88543981e302dc7e 100644
--- a/spec/features/login_spec.rb
+++ b/spec/features/login_spec.rb
@@ -36,7 +36,7 @@ feature 'Login', feature: true do
     it 'prevents the user from logging in' do
       user = create(:user, :blocked)
 
-      login_with(user)
+      gitlab_sign_in(user)
 
       expect(page).to have_content('Your account has been blocked.')
     end
@@ -44,19 +44,19 @@ feature 'Login', feature: true do
     it 'does not update Devise trackable attributes', :redis do
       user = create(:user, :blocked)
 
-      expect { login_with(user) }.not_to change { user.reload.sign_in_count }
+      expect { gitlab_sign_in(user) }.not_to change { user.reload.sign_in_count }
     end
   end
 
   describe 'with the ghost user' do
     it 'disallows login' do
-      login_with(User.ghost)
+      gitlab_sign_in(User.ghost)
 
       expect(page).to have_content('Invalid Login or password.')
     end
 
     it 'does not update Devise trackable attributes', :redis do
-      expect { login_with(User.ghost) }.not_to change { User.ghost.reload.sign_in_count }
+      expect { gitlab_sign_in(User.ghost) }.not_to change { User.ghost.reload.sign_in_count }
     end
   end
 
@@ -70,7 +70,7 @@ feature 'Login', feature: true do
       let(:user) { create(:user, :two_factor) }
 
       before do
-        login_with(user, remember: true)
+        gitlab_sign_in(user, remember: true)
         expect(page).to have_content('Two-Factor Authentication')
       end
 
@@ -122,8 +122,8 @@ feature 'Login', feature: true do
           end
 
           it 'invalidates the used code' do
-            expect { enter_code(codes.sample) }.
-              to change { user.reload.otp_backup_codes.size }.by(-1)
+            expect { enter_code(codes.sample) }
+              .to change { user.reload.otp_backup_codes.size }.by(-1)
           end
         end
 
@@ -143,31 +143,10 @@ feature 'Login', feature: true do
     end
 
     context 'logging in via OAuth' do
-      def saml_config
-        OpenStruct.new(name: 'saml', label: 'saml', args: {
-          assertion_consumer_service_url: 'https://localhost:3443/users/auth/saml/callback',
-          idp_cert_fingerprint: '26:43:2C:47:AF:F0:6B:D0:07:9C:AD:A3:74:FE:5D:94:5F:4E:9E:52',
-          idp_sso_target_url: 'https://idp.example.com/sso/saml',
-          issuer: 'https://localhost:3443/',
-          name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
-        })
-      end
-
-      def stub_omniauth_config(messages)
-        Rails.application.env_config['devise.mapping'] = Devise.mappings[:user]
-        Rails.application.routes.disable_clear_and_finalize = true
-        Rails.application.routes.draw do
-          post '/users/auth/saml' => 'omniauth_callbacks#saml'
-        end
-        allow(Gitlab::OAuth::Provider).to receive_messages(providers: [:saml], config_for: saml_config)
-        allow(Gitlab.config.omniauth).to receive_messages(messages)
-        expect_any_instance_of(Object).to receive(:omniauth_authorize_path).with(:user, "saml").and_return('/users/auth/saml')
-      end
-
       it 'shows 2FA prompt after OAuth login' do
-        stub_omniauth_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'], providers: [saml_config])
+        stub_omniauth_saml_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'], providers: [mock_saml_config])
         user = create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: 'saml')
-        login_via('saml', user, 'my-uid')
+        gitlab_sign_in_via('saml', user, 'my-uid')
 
         expect(page).to have_content('Two-Factor Authentication')
         enter_code(user.current_otp)
@@ -180,19 +159,19 @@ feature 'Login', feature: true do
     let(:user) { create(:user) }
 
     it 'allows basic login' do
-      login_with(user)
+      gitlab_sign_in(user)
       expect(current_path).to eq root_path
     end
 
     it 'does not show a "You are already signed in." error message' do
-      login_with(user)
+      gitlab_sign_in(user)
       expect(page).not_to have_content('You are already signed in.')
     end
 
     it 'blocks invalid login' do
       user = create(:user, password: 'not-the-default')
 
-      login_with(user)
+      gitlab_sign_in(user)
       expect(page).to have_content('Invalid Login or password.')
     end
   end
@@ -209,7 +188,7 @@ feature 'Login', feature: true do
       context 'with grace period defined' do
         before do
           stub_application_setting(two_factor_grace_period: 48)
-          login_with(user)
+          gitlab_sign_in(user)
         end
 
         context 'within the grace period' do
@@ -246,7 +225,7 @@ feature 'Login', feature: true do
       context 'without grace period defined' do
         before do
           stub_application_setting(two_factor_grace_period: 0)
-          login_with(user)
+          gitlab_sign_in(user)
         end
 
         it 'redirects to two-factor configuration page' do
@@ -269,7 +248,7 @@ feature 'Login', feature: true do
       context 'with grace period defined' do
         before do
           stub_application_setting(two_factor_grace_period: 48)
-          login_with(user)
+          gitlab_sign_in(user)
         end
 
         context 'within the grace period' do
@@ -310,7 +289,7 @@ feature 'Login', feature: true do
       context 'without grace period defined' do
         before do
           stub_application_setting(two_factor_grace_period: 0)
-          login_with(user)
+          gitlab_sign_in(user)
         end
 
         it 'redirects to two-factor configuration page' do
diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb
index ba930de937d272ede68ace0d7acbe4aef782b2ab..534be3ab5a776a7e5e02c6ed388b957c471af90c 100644
--- a/spec/features/markdown_spec.rb
+++ b/spec/features/markdown_spec.rb
@@ -58,8 +58,8 @@ describe 'GitLab Markdown', feature: true do
       end
 
       it 'allows Markdown in tables' do
-        expect(doc.at_css('td:contains("Baz")').children.to_html).
-          to eq '<strong>Baz</strong>'
+        expect(doc.at_css('td:contains("Baz")').children.to_html)
+          .to eq '<strong>Baz</strong>'
       end
 
       it 'parses fenced code blocks' do
@@ -158,14 +158,14 @@ describe 'GitLab Markdown', feature: true do
     describe 'Edge Cases' do
       it 'allows markup inside link elements' do
         aggregate_failures do
-          expect(doc.at_css('a[href="#link-emphasis"]').to_html).
-            to eq %{<a href="#link-emphasis"><em>text</em></a>}
+          expect(doc.at_css('a[href="#link-emphasis"]').to_html)
+            .to eq %{<a href="#link-emphasis"><em>text</em></a>}
 
-          expect(doc.at_css('a[href="#link-strong"]').to_html).
-            to eq %{<a href="#link-strong"><strong>text</strong></a>}
+          expect(doc.at_css('a[href="#link-strong"]').to_html)
+            .to eq %{<a href="#link-strong"><strong>text</strong></a>}
 
-          expect(doc.at_css('a[href="#link-code"]').to_html).
-            to eq %{<a href="#link-code"><code>text</code></a>}
+          expect(doc.at_css('a[href="#link-code"]').to_html)
+            .to eq %{<a href="#link-code"><code>text</code></a>}
         end
       end
     end
diff --git a/spec/features/merge_requests/assign_issues_spec.rb b/spec/features/merge_requests/assign_issues_spec.rb
index b306e2f5f757a6bc1068bf242b41f2a4c0a7b02b..9d9a31ab8e84841042f0fb2a9c40f0e89bb72341 100644
--- a/spec/features/merge_requests/assign_issues_spec.rb
+++ b/spec/features/merge_requests/assign_issues_spec.rb
@@ -13,8 +13,8 @@ feature 'Merge request issue assignment', js: true, feature: true do
   end
 
   def visit_merge_request(current_user = nil)
-    login_as(current_user || user)
-    visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+    gitlab_sign_in(current_user || user)
+    visit project_merge_request_path(project, merge_request)
   end
 
   context 'logged in as author' do
diff --git a/spec/features/merge_requests/award_spec.rb b/spec/features/merge_requests/award_spec.rb
index ac260e118d00c9d9e8e0e263c443ad984470ba63..ed5a4fa5784d7fd85dca14affc5cd39ba19b068d 100644
--- a/spec/features/merge_requests/award_spec.rb
+++ b/spec/features/merge_requests/award_spec.rb
@@ -7,8 +7,8 @@ feature 'Merge request awards', js: true, feature: true do
 
   describe 'logged in' do
     before do
-      login_as(user)
-      visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+      gitlab_sign_in(user)
+      visit project_merge_request_path(project, merge_request)
     end
 
     it 'adds award to merge request' do
@@ -16,7 +16,7 @@ feature 'Merge request awards', js: true, feature: true do
       expect(page).to have_selector('.js-emoji-btn.active')
       expect(first('.js-emoji-btn')).to have_content '1'
 
-      visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+      visit project_merge_request_path(project, merge_request)
       expect(first('.js-emoji-btn')).to have_content '1'
     end
 
@@ -25,7 +25,7 @@ feature 'Merge request awards', js: true, feature: true do
       find('.js-emoji-btn.active').click
       expect(first('.js-emoji-btn')).to have_content '0'
 
-      visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+      visit project_merge_request_path(project, merge_request)
       expect(first('.js-emoji-btn')).to have_content '0'
     end
 
@@ -39,7 +39,7 @@ feature 'Merge request awards', js: true, feature: true do
 
   describe 'logged out' do
     before do
-      visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+      visit project_merge_request_path(project, merge_request)
     end
 
     it 'does not see award menu button' do
diff --git a/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb b/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb
index fa306c02a43f09dfbf0d29b0dc4c094cb84db317..0f8ab4cd92bc3426e90c3c6addde71323ae090ab 100644
--- a/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb
+++ b/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb
@@ -6,7 +6,7 @@ feature 'Check if mergeable with unresolved discussions', js: true, feature: tru
   let!(:merge_request) { create(:merge_request_with_diff_notes, source_project: project, author: user) }
 
   before do
-    login_as user
+    gitlab_sign_in user
     project.team << [user, :master]
   end
 
@@ -64,6 +64,6 @@ feature 'Check if mergeable with unresolved discussions', js: true, feature: tru
   end
 
   def visit_merge_request(merge_request)
-    visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request)
+    visit project_merge_request_path(merge_request.project, merge_request)
   end
 end
diff --git a/spec/features/merge_requests/cherry_pick_spec.rb b/spec/features/merge_requests/cherry_pick_spec.rb
index 6ba681e36f7377efa3bd2e2020b096ca436c0b61..5a5f884c6b3579622e94b2bb7e8a7925df139d2f 100644
--- a/spec/features/merge_requests/cherry_pick_spec.rb
+++ b/spec/features/merge_requests/cherry_pick_spec.rb
@@ -7,7 +7,7 @@ describe 'Cherry-pick Merge Requests', js: true do
   let(:merge_request) { create(:merge_request_with_diffs, source_project: project, author: user) }
 
   before do
-    login_as user
+    gitlab_sign_in user
     project.team << [user, :master]
   end
 
@@ -28,7 +28,7 @@ describe 'Cherry-pick Merge Requests', js: true do
       end
 
       it "doesn't show a Cherry-pick button" do
-        visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+        visit project_merge_request_path(project, merge_request)
 
         expect(page).not_to have_link "Cherry-pick"
       end
@@ -36,7 +36,7 @@ describe 'Cherry-pick Merge Requests', js: true do
 
     context "With a merge commit" do
       it "shows a Cherry-pick button" do
-        visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+        visit project_merge_request_path(project, merge_request)
 
         expect(page).to have_link "Cherry-pick"
       end
diff --git a/spec/features/merge_requests/closes_issues_spec.rb b/spec/features/merge_requests/closes_issues_spec.rb
index e627618042a52600bffd37e83a13850aa4cbc35f..2f639b54637a57261acc0ccf2b9bd67ad42303b7 100644
--- a/spec/features/merge_requests/closes_issues_spec.rb
+++ b/spec/features/merge_requests/closes_issues_spec.rb
@@ -20,9 +20,9 @@ feature 'Merge Request closing issues message', feature: true, js: true do
   before do
     project.team << [user, :master]
 
-    login_as user
+    gitlab_sign_in user
 
-    visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+    visit project_merge_request_path(project, merge_request)
     wait_for_requests
   end
 
diff --git a/spec/features/merge_requests/conflicts_spec.rb b/spec/features/merge_requests/conflicts_spec.rb
index 9409c32104bfcca9aedb5ea1b0250fc537b5b1f1..a9947381f46e6e1891b3ae713ce11ab8c24de6e8 100644
--- a/spec/features/merge_requests/conflicts_spec.rb
+++ b/spec/features/merge_requests/conflicts_spec.rb
@@ -79,14 +79,14 @@ feature 'Merge request conflict resolution', js: true, feature: true do
   context 'can be resolved in the UI' do
     before do
       project.team << [user, :developer]
-      login_as(user)
+      gitlab_sign_in(user)
     end
 
     context 'the conflicts are resolvable' do
       let(:merge_request) { create_merge_request('conflict-resolvable') }
 
       before do
-        visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+        visit project_merge_request_path(project, merge_request)
       end
 
       it 'shows a link to the conflict resolution page' do
@@ -117,7 +117,7 @@ feature 'Merge request conflict resolution', js: true, feature: true do
       let(:merge_request) { create_merge_request('conflict-contains-conflict-markers') }
 
       before do
-        visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+        visit project_merge_request_path(project, merge_request)
         click_link('conflicts', href: /\/conflicts\Z/)
       end
 
@@ -164,9 +164,9 @@ feature 'Merge request conflict resolution', js: true, feature: true do
 
       before do
         project.team << [user, :developer]
-        login_as(user)
+        gitlab_sign_in(user)
 
-        visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+        visit project_merge_request_path(project, merge_request)
       end
 
       it 'does not show a link to the conflict resolution page' do
diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb
index 82987c768d14ec884b62f6e48f815393671a0700..198fcba4e78db444964c056e0c41582d564eb1c7 100644
--- a/spec/features/merge_requests/create_new_mr_spec.rb
+++ b/spec/features/merge_requests/create_new_mr_spec.rb
@@ -7,11 +7,11 @@ feature 'Create New Merge Request', feature: true, js: true do
   before do
     project.team << [user, :master]
 
-    login_as user
+    gitlab_sign_in user
   end
 
   it 'selects the source branch sha when a tag with the same name exists' do
-    visit namespace_project_merge_requests_path(project.namespace, project)
+    visit project_merge_requests_path(project)
 
     click_link 'New merge request'
     expect(page).to have_content('Source branch')
@@ -24,7 +24,7 @@ feature 'Create New Merge Request', feature: true, js: true do
   end
 
   it 'selects the target branch sha when a tag with the same name exists' do
-    visit namespace_project_merge_requests_path(project.namespace, project)
+    visit project_merge_requests_path(project)
 
     click_link 'New merge request'
 
@@ -38,7 +38,7 @@ feature 'Create New Merge Request', feature: true, js: true do
   end
 
   it 'generates a diff for an orphaned branch' do
-    visit namespace_project_merge_requests_path(project.namespace, project)
+    visit project_merge_requests_path(project)
 
     page.has_link?('New Merge Request') ? click_link("New Merge Request") : click_link('New merge request')
     expect(page).to have_content('Source branch')
@@ -65,7 +65,7 @@ feature 'Create New Merge Request', feature: true, js: true do
     it 'does not leak the private project name & namespace' do
       private_project = create(:project, :private)
 
-      visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { target_project_id: private_project.id })
+      visit project_new_merge_request_path(project, merge_request: { target_project_id: private_project.id })
 
       expect(page).not_to have_content private_project.path_with_namespace
       expect(page).to have_content project.path_with_namespace
@@ -76,7 +76,7 @@ feature 'Create New Merge Request', feature: true, js: true do
     it 'does not leak the private project name & namespace' do
       private_project = create(:project, :private)
 
-      visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { source_project_id: private_project.id })
+      visit project_new_merge_request_path(project, merge_request: { source_project_id: private_project.id })
 
       expect(page).not_to have_content private_project.path_with_namespace
       expect(page).to have_content project.path_with_namespace
@@ -84,13 +84,13 @@ feature 'Create New Merge Request', feature: true, js: true do
   end
 
   it 'populates source branch button' do
-    visit new_namespace_project_merge_request_path(project.namespace, project, change_branches: true, merge_request: { target_branch: 'master', source_branch: 'fix' })
+    visit project_new_merge_request_path(project, change_branches: true, merge_request: { target_branch: 'master', source_branch: 'fix' })
 
     expect(find('.js-source-branch')).to have_content('fix')
   end
 
   it 'allows to change the diff view' do
-    visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { target_branch: 'master', source_branch: 'fix' })
+    visit project_new_merge_request_path(project, merge_request: { target_branch: 'master', source_branch: 'fix' })
 
     click_link 'Changes'
 
@@ -106,7 +106,7 @@ feature 'Create New Merge Request', feature: true, js: true do
   end
 
   it 'does not allow non-existing branches' do
-    visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { target_branch: 'non-exist-target', source_branch: 'non-exist-source' })
+    visit project_new_merge_request_path(project, merge_request: { target_branch: 'non-exist-target', source_branch: 'non-exist-source' })
 
     expect(page).to have_content('The form contains the following errors')
     expect(page).to have_content('Source branch "non-exist-source" does not exist')
@@ -115,7 +115,7 @@ feature 'Create New Merge Request', feature: true, js: true do
 
   context 'when a branch contains commits that both delete and add the same image' do
     it 'renders the diff successfully' do
-      visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { target_branch: 'master', source_branch: 'deleted-image-test' })
+      visit project_new_merge_request_path(project, merge_request: { target_branch: 'master', source_branch: 'deleted-image-test' })
 
       click_link "Changes"
 
@@ -125,7 +125,7 @@ feature 'Create New Merge Request', feature: true, js: true do
 
   # Isolates a regression (see #24627)
   it 'does not show error messages on initial form' do
-    visit new_namespace_project_merge_request_path(project.namespace, project)
+    visit project_new_merge_request_path(project)
     expect(page).not_to have_selector('#error_explanation')
     expect(page).not_to have_content('The form contains the following error')
   end
@@ -138,8 +138,8 @@ feature 'Create New Merge Request', feature: true, js: true do
     end
 
     it 'shows pipelines for a new merge request' do
-      visit new_namespace_project_merge_request_path(
-        project.namespace, project,
+      visit project_new_merge_request_path(
+        project,
         merge_request: { target_branch: 'master', source_branch: 'fix' })
 
       page.within('.merge-request') do
diff --git a/spec/features/merge_requests/created_from_fork_spec.rb b/spec/features/merge_requests/created_from_fork_spec.rb
index b43277433830ac4beb2b78a472aabf4d081f5f1e..9f1b6be67d4062849c2b465d1aa2c0c3ee760569 100644
--- a/spec/features/merge_requests/created_from_fork_spec.rb
+++ b/spec/features/merge_requests/created_from_fork_spec.rb
@@ -16,7 +16,7 @@ feature 'Merge request created from fork' do
 
   background do
     fork_project.team << [user, :master]
-    login_as user
+    gitlab_sign_in user
   end
 
   scenario 'user can access merge request' do
@@ -64,7 +64,6 @@ feature 'Merge request created from fork' do
   end
 
   def visit_merge_request(mr)
-    visit namespace_project_merge_request_path(project.namespace,
-                                               project, mr)
+    visit project_merge_request_path(project, mr)
   end
 end
diff --git a/spec/features/merge_requests/deleted_source_branch_spec.rb b/spec/features/merge_requests/deleted_source_branch_spec.rb
index 1723fb7d3658ed7b833fd711ca9e5f6a3db12afe..671c17cd9e335aae6ffaec74126dde96cdddd6c6 100644
--- a/spec/features/merge_requests/deleted_source_branch_spec.rb
+++ b/spec/features/merge_requests/deleted_source_branch_spec.rb
@@ -8,14 +8,10 @@ describe 'Deleted source branch', feature: true, js: true do
   let(:merge_request) { create(:merge_request) }
 
   before do
-    login_as user
+    gitlab_sign_in user
     merge_request.project.team << [user, :master]
     merge_request.update!(source_branch: 'this-branch-does-not-exist')
-    visit namespace_project_merge_request_path(
-      merge_request.project.namespace,
-      merge_request.project,
-      merge_request
-    )
+    visit project_merge_request_path(merge_request.project, merge_request)
   end
 
   it 'shows a message about missing source branch' do
diff --git a/spec/features/merge_requests/diff_notes_avatars_spec.rb b/spec/features/merge_requests/diff_notes_avatars_spec.rb
index e23dc2cd94093da1451f9fa59af8cd7da2ce5e91..1b45bb7386390425ba4d30c0648990e1b4afac99 100644
--- a/spec/features/merge_requests/diff_notes_avatars_spec.rb
+++ b/spec/features/merge_requests/diff_notes_avatars_spec.rb
@@ -20,12 +20,12 @@ feature 'Diff note avatars', feature: true, js: true do
 
   before do
     project.team << [user, :master]
-    login_as user
+    gitlab_sign_in user
   end
 
   context 'discussion tab' do
     before do
-      visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+      visit project_merge_request_path(project, merge_request)
     end
 
     it 'does not show avatars on discussion tab' do
@@ -50,7 +50,7 @@ feature 'Diff note avatars', feature: true, js: true do
 
   context 'commit view' do
     before do
-      visit namespace_project_commit_path(project.namespace, project, merge_request.commits.first.id)
+      visit project_commit_path(project, merge_request.commits.first.id)
     end
 
     it 'does not render avatar after commenting' do
@@ -65,7 +65,7 @@ feature 'Diff note avatars', feature: true, js: true do
         wait_for_requests
       end
 
-      visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+      visit project_merge_request_path(project, merge_request)
 
       expect(page).to have_content('test comment')
       expect(page).not_to have_selector('.js-avatar-container')
@@ -76,7 +76,7 @@ feature 'Diff note avatars', feature: true, js: true do
   %w(inline parallel).each do |view|
     context "#{view} view" do
       before do
-        visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: view)
+        visit diffs_project_merge_request_path(project, merge_request, view: view)
 
         wait_for_requests
       end
@@ -168,7 +168,7 @@ feature 'Diff note avatars', feature: true, js: true do
         before do
           create_list(:diff_note_on_merge_request, 3, project: project, noteable: merge_request, in_reply_to: note)
 
-          visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: view)
+          visit diffs_project_merge_request_path(project, merge_request, view: view)
 
           wait_for_requests
         end
diff --git a/spec/features/merge_requests/diff_notes_resolve_spec.rb b/spec/features/merge_requests/diff_notes_resolve_spec.rb
index 4d549f3bdbb3ba33e984cf46121a36efcc365220..21dce18508555ccae20872cf0452ea31e199ce5e 100644
--- a/spec/features/merge_requests/diff_notes_resolve_spec.rb
+++ b/spec/features/merge_requests/diff_notes_resolve_spec.rb
@@ -19,7 +19,7 @@ feature 'Diff notes resolve', feature: true, js: true do
   context 'no discussions' do
     before do
       project.team << [user, :master]
-      login_as user
+      gitlab_sign_in user
       note.destroy
       visit_merge_request
     end
@@ -33,7 +33,7 @@ feature 'Diff notes resolve', feature: true, js: true do
   context 'as authorized user' do
     before do
       project.team << [user, :master]
-      login_as user
+      gitlab_sign_in user
       visit_merge_request
     end
 
@@ -402,7 +402,7 @@ feature 'Diff notes resolve', feature: true, js: true do
 
     before do
       project.team << [guest, :guest]
-      login_as guest
+      gitlab_sign_in guest
     end
 
     context 'someone elses merge request' do
@@ -494,6 +494,6 @@ feature 'Diff notes resolve', feature: true, js: true do
 
   def visit_merge_request(mr = nil)
     mr = mr || merge_request
-    visit namespace_project_merge_request_path(mr.project.namespace, mr.project, mr)
+    visit project_merge_request_path(mr.project, mr)
   end
 end
diff --git a/spec/features/merge_requests/diffs_spec.rb b/spec/features/merge_requests/diffs_spec.rb
index 44013df3ea043795f276efadf032808297e68ad2..35976b615ade0fe4e514063a2baf860eb53be458 100644
--- a/spec/features/merge_requests/diffs_spec.rb
+++ b/spec/features/merge_requests/diffs_spec.rb
@@ -12,7 +12,7 @@ feature 'Diffs URL', js: true, feature: true do
     it 'renders the notes' do
       create :note_on_merge_request, project: project, noteable: merge_request, note: 'Rebasing with master'
 
-      visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
+      visit diffs_project_merge_request_path(project, merge_request)
 
       # Load notes and diff through AJAX
       expect(page).to have_css('.note-text', visible: false, text: 'Rebasing with master')
@@ -26,7 +26,7 @@ feature 'Diffs URL', js: true, feature: true do
       let(:fragment) { "#note_#{note.id}" }
 
       before do
-        visit "#{diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)}#{fragment}"
+        visit "#{diffs_project_merge_request_path(project, merge_request)}#{fragment}"
       end
 
       it 'shows expanded note' do
@@ -39,7 +39,7 @@ feature 'Diffs URL', js: true, feature: true do
       let(:fragment) { "#note_#{note.id}" }
 
       before do
-        visit "#{diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)}#{fragment}"
+        visit "#{diffs_project_merge_request_path(project, merge_request)}#{fragment}"
       end
 
       it 'shows expanded note' do
@@ -52,7 +52,7 @@ feature 'Diffs URL', js: true, feature: true do
     it 'displays warning' do
       allow(Commit).to receive(:max_diff_options).and_return(max_files: 3)
 
-      visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
+      visit diffs_project_merge_request_path(project, merge_request)
 
       page.within('.alert') do
         expect(page).to have_text("Too many changes to show. Plain diff Email patch To preserve
@@ -74,9 +74,8 @@ feature 'Diffs URL', js: true, feature: true do
 
     context 'as author' do
       it 'shows direct edit link' do
-        login_as(author_user)
-
-        visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
+        gitlab_sign_in(author_user)
+        visit diffs_project_merge_request_path(project, merge_request)
 
         # Throws `Capybara::Poltergeist::InvalidSelector` if we try to use `#hash` syntax
         expect(page).to have_selector("[id=\"#{changelog_id}\"] a.js-edit-blob")
@@ -85,9 +84,8 @@ feature 'Diffs URL', js: true, feature: true do
 
     context 'as user who needs to fork' do
       it 'shows fork/cancel confirmation' do
-        login_as(user)
-
-        visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
+        gitlab_sign_in(user)
+        visit diffs_project_merge_request_path(project, merge_request)
 
         # Throws `Capybara::Poltergeist::InvalidSelector` if we try to use `#hash` syntax
         find("[id=\"#{changelog_id}\"] .js-edit-blob").click
diff --git a/spec/features/merge_requests/discussion_spec.rb b/spec/features/merge_requests/discussion_spec.rb
index 9db235f35ba8641121d77f955b7e7be00e2457d1..a50f66cfc6439216a34fa0316e08e35ed3757760 100644
--- a/spec/features/merge_requests/discussion_spec.rb
+++ b/spec/features/merge_requests/discussion_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 feature 'Merge Request Discussions', feature: true do
   before do
-    login_as :admin
+    gitlab_sign_in :admin
   end
 
   describe "Diff discussions" do
@@ -27,13 +27,13 @@ feature 'Merge Request Discussions', feature: true do
     let(:outdated_diff_refs) { project.commit("874797c3a73b60d2187ed6e2fcabd289ff75171e").diff_refs }
 
     before(:each) do
-      visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+      visit project_merge_request_path(project, merge_request)
     end
 
     context 'active discussions' do
       it 'shows a link to the diff' do
         within(".discussion[data-discussion-id='#{active_discussion.id}']") do
-          path = diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, anchor: active_discussion.line_code)
+          path = diffs_project_merge_request_path(project, merge_request, anchor: active_discussion.line_code)
           expect(page).to have_link('the diff', href: path)
         end
       end
@@ -42,7 +42,7 @@ feature 'Merge Request Discussions', feature: true do
     context 'outdated discussions' do
       it 'shows a link to the outdated diff' do
         within(".discussion[data-discussion-id='#{outdated_discussion.id}']") do
-          path = diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, diff_id: old_merge_request_diff.id, anchor: outdated_discussion.line_code)
+          path = diffs_project_merge_request_path(project, merge_request, diff_id: old_merge_request_diff.id, anchor: outdated_discussion.line_code)
           expect(page).to have_link('an old version of the diff', href: path)
         end
       end
@@ -72,7 +72,7 @@ feature 'Merge Request Discussions', feature: true do
     end
 
     before(:each) do
-      visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+      visit project_merge_request_path(project, merge_request)
     end
 
     context 'a regular commit comment' do
diff --git a/spec/features/merge_requests/edit_mr_spec.rb b/spec/features/merge_requests/edit_mr_spec.rb
index c77a5c68bc68d096b1186663303aecf80f856a7c..8ee78526232ed3f438f551cc93ec6def5f34d1df 100644
--- a/spec/features/merge_requests/edit_mr_spec.rb
+++ b/spec/features/merge_requests/edit_mr_spec.rb
@@ -8,9 +8,9 @@ feature 'Edit Merge Request', feature: true do
   before do
     project.team << [user, :master]
 
-    login_as user
+    gitlab_sign_in user
 
-    visit edit_namespace_project_merge_request_path(project.namespace, project, merge_request)
+    visit edit_project_merge_request_path(project, merge_request)
   end
 
   context 'editing a MR' do
@@ -33,7 +33,7 @@ feature 'Edit Merge Request', feature: true do
       merge_request.update(merge_params: { 'force_remove_source_branch' => '1' })
       expect(merge_request.merge_params['force_remove_source_branch']).to be_truthy
 
-      visit edit_namespace_project_merge_request_path(project.namespace, project, merge_request)
+      visit edit_project_merge_request_path(project, merge_request)
       uncheck 'Remove source branch when merge request is accepted'
 
       click_button 'Save changes'
diff --git a/spec/features/merge_requests/filter_by_labels_spec.rb b/spec/features/merge_requests/filter_by_labels_spec.rb
index 32a9082b9b913f45a7b911955400dd106863721a..e3d48128aeb9879e8546cb3b76589adcc67f8d14 100644
--- a/spec/features/merge_requests/filter_by_labels_spec.rb
+++ b/spec/features/merge_requests/filter_by_labels_spec.rb
@@ -26,9 +26,9 @@ feature 'Issue filtering by Labels', feature: true, js: true do
     mr3.labels << feature
 
     project.team << [user, :master]
-    login_as(user)
+    gitlab_sign_in(user)
 
-    visit namespace_project_merge_requests_path(project.namespace, project)
+    visit project_merge_requests_path(project)
   end
 
   context 'filter by label bug' do
diff --git a/spec/features/merge_requests/filter_by_milestone_spec.rb b/spec/features/merge_requests/filter_by_milestone_spec.rb
index 265a0cfc1980fac61373c4a200622578eafae53c..79bca0c9de2523c7be591910bde9ea09a68d434d 100644
--- a/spec/features/merge_requests/filter_by_milestone_spec.rb
+++ b/spec/features/merge_requests/filter_by_milestone_spec.rb
@@ -15,7 +15,7 @@ feature 'Merge Request filtering by Milestone', feature: true do
 
   before do
     project.team << [user, :master]
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   scenario 'filters by no Milestone', js: true do
diff --git a/spec/features/merge_requests/filter_merge_requests_spec.rb b/spec/features/merge_requests/filter_merge_requests_spec.rb
index d086be70d69d790d2492886a7f781aeb4cd0ea91..2a62cda6d84437484c4ba624a2d86ffc0e18d3dc 100644
--- a/spec/features/merge_requests/filter_merge_requests_spec.rb
+++ b/spec/features/merge_requests/filter_merge_requests_spec.rb
@@ -14,10 +14,10 @@ describe 'Filter merge requests', feature: true do
   before do
     project.team << [user, :master]
     group.add_developer(user)
-    login_as(user)
+    gitlab_sign_in(user)
     create(:merge_request, source_project: project, target_project: project)
 
-    visit namespace_project_merge_requests_path(project.namespace, project)
+    visit project_merge_requests_path(project)
   end
 
   describe 'for assignee from mr#index' do
@@ -191,7 +191,7 @@ describe 'Filter merge requests', feature: true do
         assignee: user)
       mr.labels << bug_label
 
-      visit namespace_project_merge_requests_path(project.namespace, project)
+      visit project_merge_requests_path(project)
     end
 
     context 'only text', js: true do
@@ -275,7 +275,7 @@ describe 'Filter merge requests', feature: true do
       mr1.labels << bug_label
       mr2.labels << bug_label
 
-      visit namespace_project_merge_requests_path(project.namespace, project)
+      visit project_merge_requests_path(project)
     end
 
     it 'is able to filter and sort merge requests' do
@@ -297,7 +297,7 @@ describe 'Filter merge requests', feature: true do
 
   describe 'filter by assignee id', js: true do
     it 'filter by current user' do
-      visit namespace_project_merge_requests_path(project.namespace, project, assignee_id: user.id)
+      visit project_merge_requests_path(project, assignee_id: user.id)
 
       expect_tokens([{ name: 'assignee', value: "@#{user.username}" }])
       expect_filtered_search_input_empty
@@ -307,7 +307,7 @@ describe 'Filter merge requests', feature: true do
       new_user = create(:user)
       project.add_developer(new_user)
 
-      visit namespace_project_merge_requests_path(project.namespace, project, assignee_id: new_user.id)
+      visit project_merge_requests_path(project, assignee_id: new_user.id)
 
       expect_tokens([{ name: 'assignee', value: "@#{new_user.username}" }])
       expect_filtered_search_input_empty
@@ -316,7 +316,7 @@ describe 'Filter merge requests', feature: true do
 
   describe 'filter by author id', js: true do
     it 'filter by current user' do
-      visit namespace_project_merge_requests_path(project.namespace, project, author_id: user.id)
+      visit project_merge_requests_path(project, author_id: user.id)
 
       expect_tokens([{ name: 'author', value: "@#{user.username}" }])
       expect_filtered_search_input_empty
@@ -326,7 +326,7 @@ describe 'Filter merge requests', feature: true do
       new_user = create(:user)
       project.add_developer(new_user)
 
-      visit namespace_project_merge_requests_path(project.namespace, project, author_id: new_user.id)
+      visit project_merge_requests_path(project, author_id: new_user.id)
 
       expect_tokens([{ name: 'author', value: "@#{new_user.username}" }])
       expect_filtered_search_input_empty
diff --git a/spec/features/merge_requests/form_spec.rb b/spec/features/merge_requests/form_spec.rb
index 00ef1ffdddcb6ff7d571ad808fb7a1e352e141e9..8f2857c66f7ccfb4791db0a7a7ef2cab48bb254c 100644
--- a/spec/features/merge_requests/form_spec.rb
+++ b/spec/features/merge_requests/form_spec.rb
@@ -1,8 +1,6 @@
 require 'rails_helper'
 
 describe 'New/edit merge request', feature: true, js: true do
-  include GitlabRoutingHelper
-
   let!(:project)   { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
   let(:fork_project) { create(:project, forked_from_project: project) }
   let!(:user)      { create(:user)}
@@ -18,13 +16,12 @@ describe 'New/edit merge request', feature: true, js: true do
 
   context 'owned projects' do
     before do
-      login_as(user)
+      gitlab_sign_in(user)
     end
 
     context 'new merge request' do
       before do
-        visit new_namespace_project_merge_request_path(
-          project.namespace,
+        visit project_new_merge_request_path(
           project,
           merge_request: {
             source_project_id: project.id,
@@ -96,6 +93,13 @@ describe 'New/edit merge request', feature: true, js: true do
             .to end_with(merge_request_path(merge_request))
         end
       end
+
+      it 'description has autocomplete' do
+        find('#merge_request_description').native.send_keys('')
+        fill_in 'merge_request_description', with: '@'
+
+        expect(page).to have_selector('.atwho-view')
+      end
     end
 
     context 'edit merge request' do
@@ -107,7 +111,7 @@ describe 'New/edit merge request', feature: true, js: true do
                                  target_branch: 'master'
                               )
 
-        visit edit_namespace_project_merge_request_path(project.namespace, project, merge_request)
+        visit edit_project_merge_request_path(project, merge_request)
       end
 
       it 'updates merge request' do
@@ -157,19 +161,25 @@ describe 'New/edit merge request', feature: true, js: true do
           end
         end
       end
+
+      it 'description has autocomplete' do
+        find('#merge_request_description').native.send_keys('')
+        fill_in 'merge_request_description', with: '@'
+
+        expect(page).to have_selector('.atwho-view')
+      end
     end
   end
 
   context 'forked project' do
     before do
       fork_project.team << [user, :master]
-      login_as(user)
+      gitlab_sign_in(user)
     end
 
     context 'new merge request' do
       before do
-        visit new_namespace_project_merge_request_path(
-          fork_project.namespace,
+        visit project_new_merge_request_path(
           fork_project,
           merge_request: {
             source_project_id: fork_project.id,
@@ -237,7 +247,7 @@ describe 'New/edit merge request', feature: true, js: true do
                                  target_branch: 'master'
                               )
 
-        visit edit_namespace_project_merge_request_path(project.namespace, project, merge_request)
+        visit edit_project_merge_request_path(project, merge_request)
       end
 
       it 'should update merge request' do
diff --git a/spec/features/merge_requests/merge_commit_message_toggle_spec.rb b/spec/features/merge_requests/merge_commit_message_toggle_spec.rb
index 221ddb5873c0d57ac506cb9632cd74ecec9e4af6..831c60625f45a197e796c5272857dcef0d83d2b9 100644
--- a/spec/features/merge_requests/merge_commit_message_toggle_spec.rb
+++ b/spec/features/merge_requests/merge_commit_message_toggle_spec.rb
@@ -34,9 +34,9 @@ feature 'Clicking toggle commit message link', feature: true, js: true do
   before do
     project.team << [user, :master]
 
-    login_as user
+    gitlab_sign_in user
 
-    visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+    visit project_merge_request_path(project, merge_request)
 
     expect(page).not_to have_selector('.js-commit-message')
     click_button "Modify commit message"
diff --git a/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb b/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb
index 836a7b6e09a05c3850bca18d34edbec7b980d23e..716f829295ea5e77f24c667cdf03fc8431456074 100644
--- a/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb
+++ b/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb
@@ -28,8 +28,8 @@ feature 'Merge immediately', :feature, :js do
     end
 
     before do
-      login_as user
-      visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request)
+      gitlab_sign_in user
+      visit project_merge_request_path(merge_request.project, merge_request)
     end
 
     it 'enables merge immediately' do
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 09f889d4dd6b71b7f08d2ac6f772e7e35c0f2b7f..2a4178a819b7b89744f38aa6fcb0aa22c0f93e54 100644
--- a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb
+++ b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb
@@ -28,7 +28,7 @@ feature 'Merge When Pipeline Succeeds', :feature, :js do
     end
 
     before do
-      login_as user
+      gitlab_sign_in user
       visit_merge_request(merge_request)
     end
 
@@ -121,7 +121,7 @@ feature 'Merge When Pipeline Succeeds', :feature, :js do
     end
 
     before do
-      login_as user
+      gitlab_sign_in user
       visit_merge_request(merge_request)
     end
 
@@ -155,6 +155,6 @@ feature 'Merge When Pipeline Succeeds', :feature, :js do
   end
 
   def visit_merge_request(merge_request)
-    visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request)
+    visit project_merge_request_path(merge_request.project, merge_request)
   end
 end
diff --git a/spec/features/merge_requests/mini_pipeline_graph_spec.rb b/spec/features/merge_requests/mini_pipeline_graph_spec.rb
index 3a11ea3c8b2793411ebcdb87f54e92553e87908e..2c0632a4e8292be669f9d29d1bb6eb1b418b14c5 100644
--- a/spec/features/merge_requests/mini_pipeline_graph_spec.rb
+++ b/spec/features/merge_requests/mini_pipeline_graph_spec.rb
@@ -11,12 +11,12 @@ feature 'Mini Pipeline Graph', :js, :feature do
   before do
     build.run
 
-    login_as(user)
+    gitlab_sign_in(user)
     visit_merge_request
   end
 
   def visit_merge_request(format = :html)
-    visit namespace_project_merge_request_path(project.namespace, project, merge_request, format: format)
+    visit project_merge_request_path(project, merge_request, format: format)
   end
 
   it 'should display a mini pipeline graph' do
@@ -111,7 +111,7 @@ feature 'Mini Pipeline Graph', :js, :feature do
         build_item.click
         find('.build-page')
 
-        expect(current_path).to eql(namespace_project_job_path(project.namespace, project, build))
+        expect(current_path).to eql(project_job_path(project, build))
       end
 
       it 'should show tooltip when hovered' do
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 b1dc81a606ab27b40a656ebf8fb0c4cc1a5d9b62..6bcfef71d253dba4e7443b140a0f230dd4279350 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
@@ -5,7 +5,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu
   let(:project)       { merge_request.target_project }
 
   before do
-    login_as merge_request.author
+    gitlab_sign_in merge_request.author
 
     project.team << [merge_request.author, :master]
   end
@@ -145,6 +145,6 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu
   end
 
   def visit_merge_request(merge_request)
-    visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request)
+    visit project_merge_request_path(merge_request.project, merge_request)
   end
 end
diff --git a/spec/features/merge_requests/pipelines_spec.rb b/spec/features/merge_requests/pipelines_spec.rb
index 744bd484a80dc9b2fe0568769f1d2ef553ec6b5b..d55e6329a9fbe612e2d9df06d036f731e958e07b 100644
--- a/spec/features/merge_requests/pipelines_spec.rb
+++ b/spec/features/merge_requests/pipelines_spec.rb
@@ -7,7 +7,7 @@ feature 'Pipelines for Merge Requests', feature: true, js: true do
 
   before do
     project.team << [user, :master]
-    login_as user
+    gitlab_sign_in user
   end
 
   context 'with pipelines' do
@@ -19,7 +19,7 @@ feature 'Pipelines for Merge Requests', feature: true, js: true do
     end
 
     before do
-      visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+      visit project_merge_request_path(project, merge_request)
     end
 
     scenario 'user visits merge request pipelines tab' do
@@ -34,7 +34,7 @@ feature 'Pipelines for Merge Requests', feature: true, js: true do
 
   context 'without pipelines' do
     before do
-      visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+      visit project_merge_request_path(project, merge_request)
     end
 
     scenario 'user visits merge request page' do
diff --git a/spec/features/merge_requests/target_branch_spec.rb b/spec/features/merge_requests/target_branch_spec.rb
index c154cf8ade9f81f47fdb83e5d67ca1187a6ed89b..c61f817dd9aae9e52ef378a4a36aee43b6b98ea0 100644
--- a/spec/features/merge_requests/target_branch_spec.rb
+++ b/spec/features/merge_requests/target_branch_spec.rb
@@ -6,14 +6,11 @@ describe 'Target branch', feature: true, js: true do
   let(:project) { merge_request.project }
 
   def path_to_merge_request
-    namespace_project_merge_request_path(
-      project.namespace,
-      project, merge_request
-    )
+    project_merge_request_path(project, merge_request)
   end
 
   before do
-    login_as user
+    gitlab_sign_in user
     project.team << [user, :master]
   end
 
diff --git a/spec/features/merge_requests/toggle_whitespace_changes_spec.rb b/spec/features/merge_requests/toggle_whitespace_changes_spec.rb
index 0f98737b7008959e510f4932d3c1c3bde4da78c8..ae7e99d14628362f405f8cf69e67094017fc1dba 100644
--- a/spec/features/merge_requests/toggle_whitespace_changes_spec.rb
+++ b/spec/features/merge_requests/toggle_whitespace_changes_spec.rb
@@ -2,10 +2,10 @@ require 'spec_helper'
 
 feature 'Toggle Whitespace Changes', js: true, feature: true do
   before do
-    login_as :admin
+    gitlab_sign_in :admin
     merge_request = create(:merge_request)
     project = merge_request.source_project
-    visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
+    visit diffs_project_merge_request_path(project, merge_request)
   end
 
   it 'has a button to toggle whitespace changes' do
diff --git a/spec/features/merge_requests/toggler_behavior_spec.rb b/spec/features/merge_requests/toggler_behavior_spec.rb
index 3acd3f6a8b3a9e07c25c9b68571203bc9f0180ab..219b9fd893823ce6da92b1b2f3984ce198769f65 100644
--- a/spec/features/merge_requests/toggler_behavior_spec.rb
+++ b/spec/features/merge_requests/toggler_behavior_spec.rb
@@ -8,10 +8,10 @@ feature 'toggler_behavior', js: true, feature: true do
   let(:fragment_id) { "#note_#{note.id}" }
 
   before do
-    login_as :admin
+    gitlab_sign_in :admin
     project = merge_request.source_project
     page.current_window.resize_to(1000, 300)
-    visit "#{namespace_project_merge_request_path(project.namespace, project, merge_request)}#{fragment_id}"
+    visit "#{project_merge_request_path(project, merge_request)}#{fragment_id}"
   end
 
   describe 'scroll position' do
diff --git a/spec/features/merge_requests/update_merge_requests_spec.rb b/spec/features/merge_requests/update_merge_requests_spec.rb
index bcdfdf78a44cc3b61abd3fed77736d84510ec1eb..f8f3e37719818fe2dc2fa8f517c907b1821736b2 100644
--- a/spec/features/merge_requests/update_merge_requests_spec.rb
+++ b/spec/features/merge_requests/update_merge_requests_spec.rb
@@ -7,13 +7,13 @@ feature 'Multiple merge requests updating from merge_requests#index', feature: t
 
   before do
     project.team << [user, :master]
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   context 'status', js: true do
     describe 'close merge request' do
       before do
-        visit namespace_project_merge_requests_path(project.namespace, project)
+        visit project_merge_requests_path(project)
       end
 
       it 'closes merge request' do
@@ -26,7 +26,7 @@ feature 'Multiple merge requests updating from merge_requests#index', feature: t
     describe 'reopen merge request' do
       before do
         merge_request.close
-        visit namespace_project_merge_requests_path(project.namespace, project, state: 'closed')
+        visit project_merge_requests_path(project, state: 'closed')
       end
 
       it 'reopens merge request' do
@@ -40,7 +40,7 @@ feature 'Multiple merge requests updating from merge_requests#index', feature: t
   context 'assignee', js: true do
     describe 'set assignee' do
       before do
-        visit namespace_project_merge_requests_path(project.namespace, project)
+        visit project_merge_requests_path(project)
       end
 
       it "updates merge request with assignee" do
@@ -56,7 +56,7 @@ feature 'Multiple merge requests updating from merge_requests#index', feature: t
       before do
         merge_request.assignee = user
         merge_request.save
-        visit namespace_project_merge_requests_path(project.namespace, project)
+        visit project_merge_requests_path(project)
       end
 
       it "removes assignee from the merge request" do
@@ -72,7 +72,7 @@ feature 'Multiple merge requests updating from merge_requests#index', feature: t
 
     describe 'set milestone' do
       before do
-        visit namespace_project_merge_requests_path(project.namespace, project)
+        visit project_merge_requests_path(project)
       end
 
       it "updates merge request with milestone" do
@@ -86,7 +86,7 @@ feature 'Multiple merge requests updating from merge_requests#index', feature: t
       before do
         merge_request.milestone = milestone
         merge_request.save
-        visit namespace_project_merge_requests_path(project.namespace, project)
+        visit project_merge_requests_path(project)
       end
 
       it "removes milestone from the merge request" do
diff --git a/spec/features/merge_requests/user_lists_merge_requests_spec.rb b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
index cabb8e455f9d2053d6e6fa781a7affc0f78a18ce..f541f4959956458f209a41b69d1d2d2bd2cbb0e0 100644
--- a/spec/features/merge_requests/user_lists_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
@@ -37,7 +37,7 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true
   it 'filters on no assignee' do
     visit_merge_requests(project, assignee_id: IssuableFinder::NONE)
 
-    expect(current_path).to eq(namespace_project_merge_requests_path(project.namespace, project))
+    expect(current_path).to eq(project_merge_requests_path(project))
     expect(page).to have_content 'merge_lfs'
     expect(page).not_to have_content 'fix'
     expect(page).not_to have_content 'markdown'
@@ -136,7 +136,7 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true
       end
 
       it 'sorts by recently due milestone' do
-        visit namespace_project_merge_requests_path(project.namespace, project,
+        visit project_merge_requests_path(project,
           label_name: [label.name, label2.name],
           assignee_id: user.id,
           sort: sort_value_milestone_soon)
diff --git a/spec/features/merge_requests/user_posts_diff_notes_spec.rb b/spec/features/merge_requests/user_posts_diff_notes_spec.rb
index 14bc549c9f9854beb70e492d1bf67d83ec77709d..7b1ac60231adf349882ea8b840564852aa909775 100644
--- a/spec/features/merge_requests/user_posts_diff_notes_spec.rb
+++ b/spec/features/merge_requests/user_posts_diff_notes_spec.rb
@@ -7,7 +7,7 @@ feature 'Merge requests > User posts diff notes', :js do
 
   before do
     project.add_developer(user)
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   let(:comment_button_class) { '.add-diff-note' }
@@ -17,7 +17,7 @@ feature 'Merge requests > User posts diff notes', :js do
 
   context 'when hovering over a parallel view diff file' do
     before do
-      visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: 'parallel')
+      visit diffs_project_merge_request_path(project, merge_request, view: 'parallel')
     end
 
     context 'with an old line on the left and no line on the right' do
@@ -92,7 +92,7 @@ feature 'Merge requests > User posts diff notes', :js do
 
   context 'when hovering over an inline view diff file' do
     before do
-      visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: 'inline')
+      visit diffs_project_merge_request_path(project, merge_request, view: 'inline')
     end
 
     context 'with a new line' do
@@ -136,9 +136,9 @@ feature 'Merge requests > User posts diff notes', :js do
 
     context 'when hovering over a diff discussion' do
       before do
-        visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: 'inline')
+        visit diffs_project_merge_request_path(project, merge_request, view: 'inline')
         should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]'))
-        visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+        visit project_merge_request_path(project, merge_request)
       end
 
       it 'does not allow commenting' do
@@ -149,7 +149,7 @@ feature 'Merge requests > User posts diff notes', :js do
 
   context 'when cancelling the comment addition' do
     before do
-      visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: 'inline')
+      visit diffs_project_merge_request_path(project, merge_request, view: 'inline')
     end
 
     context 'with a new line' do
@@ -161,7 +161,7 @@ feature 'Merge requests > User posts diff notes', :js do
 
   describe 'with muliple note forms' do
     before do
-      visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: 'inline')
+      visit diffs_project_merge_request_path(project, merge_request, view: 'inline')
       click_diff_line(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]'))
       click_diff_line(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_22_22"]'))
     end
@@ -181,7 +181,7 @@ feature 'Merge requests > User posts diff notes', :js do
   context 'when the MR only supports legacy diff notes' do
     before do
       merge_request.merge_request_diff.update_attributes(start_commit_sha: nil)
-      visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: 'inline')
+      visit diffs_project_merge_request_path(project, merge_request, view: 'inline')
     end
 
     context 'with a new line' do
diff --git a/spec/features/merge_requests/user_posts_notes_spec.rb b/spec/features/merge_requests/user_posts_notes_spec.rb
index 22552529b9ed1c930c61b3541056c373cedb8e73..b3c8b0e9c346506e53355bf38775ced2e75886de 100644
--- a/spec/features/merge_requests/user_posts_notes_spec.rb
+++ b/spec/features/merge_requests/user_posts_notes_spec.rb
@@ -13,8 +13,8 @@ describe 'Merge requests > User posts notes', :js do
   end
 
   before do
-    login_as :admin
-    visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+    gitlab_sign_in :admin
+    visit project_merge_request_path(project, merge_request)
   end
 
   subject { page }
@@ -22,8 +22,8 @@ describe 'Merge requests > User posts notes', :js do
   describe 'the note form' do
     it 'is valid' do
       is_expected.to have_css('.js-main-target-form', visible: true, count: 1)
-      expect(find('.js-main-target-form .js-comment-button').value).
-        to eq('Comment')
+      expect(find('.js-main-target-form .js-comment-button').value)
+        .to eq('Comment')
       page.within('.js-main-target-form') do
         expect(page).not_to have_link('Cancel')
       end
@@ -123,8 +123,8 @@ describe 'Merge requests > User posts notes', :js do
 
         page.within("#note_#{note.id}") do
           is_expected.to have_css('.note_edited_ago')
-          expect(find('.note_edited_ago').text).
-            to match(/less than a minute ago/)
+          expect(find('.note_edited_ago').text)
+            .to match(/less than a minute ago/)
         end
       end
     end
diff --git a/spec/features/merge_requests/user_sees_system_notes_spec.rb b/spec/features/merge_requests/user_sees_system_notes_spec.rb
index 55d0f9d728cdf53f84f801e1b195ec55820a81b8..385708a28c5ee30e9bb81cc8c92cd044c1ff008a 100644
--- a/spec/features/merge_requests/user_sees_system_notes_spec.rb
+++ b/spec/features/merge_requests/user_sees_system_notes_spec.rb
@@ -11,11 +11,11 @@ feature 'Merge requests > User sees system notes' do
     before do
       user = create(:user)
       private_project.add_developer(user)
-      login_as(user)
+      gitlab_sign_in(user)
     end
 
     it 'shows the system note' do
-      visit namespace_project_merge_request_path(public_project.namespace, public_project, merge_request)
+      visit project_merge_request_path(public_project, merge_request)
 
       expect(page).to have_css('.system-note')
     end
@@ -23,7 +23,7 @@ feature 'Merge requests > User sees system notes' do
 
   context 'when not logged-in' do
     it 'hides the system note' do
-      visit namespace_project_merge_request_path(public_project.namespace, public_project, merge_request)
+      visit project_merge_request_path(public_project, merge_request)
 
       expect(page).not_to have_css('.system-note')
     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 0e64a3e1a4b1752518c0375ee7bc415c6be4f43c..229dcda7ce42786dd600b7581a3dd5261032abde 100644
--- a/spec/features/merge_requests/user_uses_slash_commands_spec.rb
+++ b/spec/features/merge_requests/user_uses_slash_commands_spec.rb
@@ -1,14 +1,14 @@
 require 'rails_helper'
 
-feature 'Merge Requests > User uses slash commands', feature: true, js: true do
-  include SlashCommandsHelpers
+feature 'Merge Requests > User uses quick actions', feature: true, js: true do
+  include QuickActionsHelpers
 
   let(:user) { create(:user) }
   let(:project) { create(:project, :public) }
   let(:merge_request) { create(:merge_request, source_project: project) }
   let!(:milestone) { create(:milestone, project: project, title: 'ASAP') }
 
-  it_behaves_like 'issuable record that supports slash commands in its description and notes', :merge_request do
+  it_behaves_like 'issuable record that supports quick actions 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', target_branch: 'master' } } }
   end
@@ -16,8 +16,8 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do
   describe 'merge-request-only commands' do
     before do
       project.team << [user, :master]
-      login_with(user)
-      visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+      gitlab_sign_in(user)
+      visit project_merge_request_path(project, merge_request)
     end
 
     after do
@@ -51,9 +51,9 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do
         let(:guest) { create(:user) }
         before do
           project.team << [guest, :guest]
-          logout
-          login_with(guest)
-          visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+          gitlab_sign_out
+          gitlab_sign_in(guest)
+          visit project_merge_request_path(project, merge_request)
         end
 
         it 'does not change the WIP prefix' do
@@ -97,9 +97,9 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do
         let(:guest) { create(:user) }
         before do
           project.team << [guest, :guest]
-          logout
-          login_with(guest)
-          visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+          gitlab_sign_out
+          gitlab_sign_in(guest)
+          visit project_merge_request_path(project, merge_request)
         end
 
         it 'does not merge the MR' do
@@ -125,13 +125,13 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do
       let(:new_url_opts) { { merge_request: { source_branch: 'feature' } } }
 
       before do
-        logout
+        gitlab_sign_out
         another_project.team << [user, :master]
-        login_with(user)
+        gitlab_sign_in(user)
       end
 
       it 'changes target_branch in new merge_request' do
-        visit new_namespace_project_merge_request_path(another_project.namespace, another_project, new_url_opts)
+        visit project_new_merge_request_path(another_project, new_url_opts)
 
         fill_in "merge_request_title", with: 'My brand new feature'
         fill_in "merge_request_description", with: "le feature \n/target_branch fix\nFeature description:"
@@ -145,7 +145,7 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do
       it 'does not change target branch when merge request is edited' do
         new_merge_request = create(:merge_request, source_project: another_project)
 
-        visit edit_namespace_project_merge_request_path(another_project.namespace, another_project, new_merge_request)
+        visit edit_project_merge_request_path(another_project, new_merge_request)
         fill_in "merge_request_description", with: "Want to update target branch\n/target_branch fix\n"
         click_button "Save changes"
 
@@ -181,9 +181,9 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do
         let(:guest) { create(:user) }
         before do
           project.team << [guest, :guest]
-          logout
-          login_with(guest)
-          visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+          gitlab_sign_out
+          gitlab_sign_in(guest)
+          visit project_merge_request_path(project, merge_request)
         end
 
         it 'does not change target branch' do
diff --git a/spec/features/merge_requests/versions_spec.rb b/spec/features/merge_requests/versions_spec.rb
index aad522ee26efad75375d0073b545cff5caa98890..94fcfa398c9e92aab12fd9e3db48cd207ba32f59 100644
--- a/spec/features/merge_requests/versions_spec.rb
+++ b/spec/features/merge_requests/versions_spec.rb
@@ -8,8 +8,8 @@ feature 'Merge Request versions', js: true, feature: true do
   let!(:merge_request_diff3) { merge_request.merge_request_diffs.create(head_commit_sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e') }
 
   before do
-    login_as :admin
-    visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
+    gitlab_sign_in :admin
+    visit diffs_project_merge_request_path(project, merge_request)
   end
 
   it 'show the latest version of the diff' do
@@ -96,8 +96,7 @@ feature 'Merge Request versions', js: true, feature: true do
     end
 
     it 'has a path with comparison context' do
-      expect(page).to have_current_path diffs_namespace_project_merge_request_path(
-        project.namespace,
+      expect(page).to have_current_path diffs_project_merge_request_path(
         project,
         merge_request.iid,
         diff_id: merge_request_diff3.id,
diff --git a/spec/features/merge_requests/widget_deployments_spec.rb b/spec/features/merge_requests/widget_deployments_spec.rb
index 118ecd9cba523cac3b179b6a27ddb283e021bb01..c43c7460a08b2a545be09740c816e9a0602547c5 100644
--- a/spec/features/merge_requests/widget_deployments_spec.rb
+++ b/spec/features/merge_requests/widget_deployments_spec.rb
@@ -12,9 +12,9 @@ feature 'Widget Deployments Header', feature: true, js: true do
     given!(:manual) { }
 
     background do
-      login_as(user)
+      gitlab_sign_in(user)
       project.team << [user, role]
-      visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+      visit project_merge_request_path(project, merge_request)
     end
 
     scenario 'displays that the environment is deployed' do
diff --git a/spec/features/merge_requests/widget_spec.rb b/spec/features/merge_requests/widget_spec.rb
index 4f3a5119915538553bc755b8c693127ec7d427c5..8135411fe033ef0b12a12dca168ddf292e247961 100644
--- a/spec/features/merge_requests/widget_spec.rb
+++ b/spec/features/merge_requests/widget_spec.rb
@@ -7,13 +7,12 @@ describe 'Merge request', :feature, :js do
 
   before do
     project.team << [user, :master]
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   context 'new merge request' do
     before do
-      visit new_namespace_project_merge_request_path(
-        project.namespace,
+      visit project_new_merge_request_path(
         project,
         merge_request: {
           source_project_id: project.id,
@@ -44,7 +43,7 @@ describe 'Merge request', :feature, :js do
     end
 
     before do
-      visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+      visit project_merge_request_path(project, merge_request)
     end
 
     it 'shows environments link' do
@@ -71,7 +70,7 @@ describe 'Merge request', :feature, :js do
                        type: 'CiService',
                        category: 'ci')
 
-      visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+      visit project_merge_request_path(project, merge_request)
     end
 
     it 'has danger button while waiting for external CI status' do
@@ -92,7 +91,7 @@ describe 'Merge request', :feature, :js do
                                       head_pipeline_of: merge_request)
       create(:ci_build, :pending, pipeline: pipeline)
 
-      visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+      visit project_merge_request_path(project, merge_request)
     end
 
     it 'has danger button when not succeeded' do
@@ -112,9 +111,7 @@ describe 'Merge request', :feature, :js do
         status: :manual,
         head_pipeline_of: merge_request)
 
-      visit namespace_project_merge_request_path(project.namespace,
-                                                 project,
-                                                 merge_request)
+      visit project_merge_request_path(project, merge_request)
     end
 
     it 'shows information about blocked pipeline' do
@@ -136,7 +133,7 @@ describe 'Merge request', :feature, :js do
                                       head_pipeline_of: merge_request)
       create(:ci_build, :pending, pipeline: pipeline)
 
-      visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+      visit project_merge_request_path(project, merge_request)
     end
 
     it 'has info button when MWBS button' do
@@ -154,7 +151,7 @@ describe 'Merge request', :feature, :js do
         merge_error: 'Something went wrong'
       )
 
-      visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+      visit project_merge_request_path(project, merge_request)
     end
 
     it 'shows information about the merge error' do
@@ -175,7 +172,7 @@ describe 'Merge request', :feature, :js do
         merge_error: 'Something went wrong'
       )
 
-      visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+      visit project_merge_request_path(project, merge_request)
     end
 
     it 'shows information about the merge error' do
@@ -191,7 +188,7 @@ describe 'Merge request', :feature, :js do
   context 'merge error' do
     before do
       allow_any_instance_of(Repository).to receive(:merge).and_return(false)
-      visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+      visit project_merge_request_path(project, merge_request)
     end
 
     it 'updates the MR widget' do
@@ -209,10 +206,10 @@ describe 'Merge request', :feature, :js do
 
     before do
       project.team << [user2, :master]
-      logout
-      login_as user2
+      gitlab_sign_out
+      gitlab_sign_in user2
       merge_request.update(target_project: fork_project)
-      visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+      visit project_merge_request_path(project, merge_request)
     end
 
     it 'user can merge into the source project' do
diff --git a/spec/features/merge_requests/wip_message_spec.rb b/spec/features/merge_requests/wip_message_spec.rb
index 3311731b33bd67bb0471ee4c83a7b86ab9eeadec..224723773bf66d136434435e240192bc19db26c6 100644
--- a/spec/features/merge_requests/wip_message_spec.rb
+++ b/spec/features/merge_requests/wip_message_spec.rb
@@ -6,13 +6,12 @@ feature 'Work In Progress help message', feature: true do
 
   before do
     project.team << [user, :master]
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   context 'with WIP commits' do
     it 'shows a specific WIP hint' do
-      visit new_namespace_project_merge_request_path(
-        project.namespace,
+      visit project_new_merge_request_path(
         project,
         merge_request: {
           source_project_id: project.id,
@@ -32,8 +31,7 @@ feature 'Work In Progress help message', feature: true do
 
   context 'without WIP commits' do
     it 'shows the regular WIP message' do
-      visit new_namespace_project_merge_request_path(
-        project.namespace,
+      visit project_new_merge_request_path(
         project,
         merge_request: {
           source_project_id: project.id,
diff --git a/spec/features/milestone_spec.rb b/spec/features/milestone_spec.rb
index c07de01c594da7faa4962fd9c1a7cb6938094a97..880c53343bcf28659f529a223e134383d527118c 100644
--- a/spec/features/milestone_spec.rb
+++ b/spec/features/milestone_spec.rb
@@ -6,12 +6,12 @@ feature 'Milestone', feature: true do
 
   before do
     project.team << [user, :master]
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   feature 'Create a milestone' do
     scenario 'shows an informative message for a new milestone' do
-      visit new_namespace_project_milestone_path(project.namespace, project)
+      visit new_project_milestone_path(project)
 
       page.within '.milestone-form' do
         fill_in "milestone_title", with: '8.7'
@@ -31,7 +31,7 @@ feature 'Milestone', feature: true do
       milestone = create(:milestone, project: project, title: 8.7)
 
       create(:issue, title: "Bugfix1", project: project, milestone: milestone, state: "closed")
-      visit namespace_project_milestone_path(project.namespace, project, milestone)
+      visit project_milestone_path(project, milestone)
 
       expect(find('.alert-success')).to have_content('All issues for this milestone are closed. You may close this milestone now.')
     end
@@ -41,7 +41,7 @@ feature 'Milestone', feature: true do
     scenario 'displays validation message' do
       milestone = create(:milestone, project: project, title: 8.7)
 
-      visit new_namespace_project_milestone_path(project.namespace, project)
+      visit new_project_milestone_path(project)
       page.within '.milestone-form' do
         fill_in "milestone_title", with: milestone.title
       end
diff --git a/spec/features/milestones/milestones_spec.rb b/spec/features/milestones/milestones_spec.rb
deleted file mode 100644
index c8a4d23f695829e8d8465225ac8d87fb2b4f520b..0000000000000000000000000000000000000000
--- a/spec/features/milestones/milestones_spec.rb
+++ /dev/null
@@ -1,109 +0,0 @@
-require 'rails_helper'
-
-describe 'Milestone draggable', feature: true, js: true do
-  include DragTo
-
-  let(:milestone) { create(:milestone, project: project, title: 8.14) }
-  let(:project)   { create(:empty_project, :public) }
-  let(:user)      { create(:user) }
-
-  context 'issues' do
-    let(:issue)        { page.find_by_id('issues-list-unassigned').find('li') }
-    let(:issue_target) { page.find_by_id('issues-list-ongoing') }
-
-    it 'does not allow guest to drag issue' do
-      create_and_drag_issue
-
-      expect(issue_target).not_to have_selector('.issuable-row')
-    end
-
-    it 'does not allow authorized user to drag issue' do
-      login_as(user)
-      create_and_drag_issue
-
-      expect(issue_target).not_to have_selector('.issuable-row')
-    end
-
-    it 'allows author to drag issue' do
-      login_as(user)
-      create_and_drag_issue(author: user)
-
-      expect(issue_target).to have_selector('.issuable-row')
-    end
-
-    it 'allows admin to drag issue' do
-      login_as(:admin)
-      create_and_drag_issue
-
-      expect(issue_target).to have_selector('.issuable-row')
-    end
-
-    it 'assigns issue when it has been dragged to ongoing list' do
-      login_as(:admin)
-      create_and_drag_issue
-
-      expect(@issue.reload.assignees).not_to be_empty
-      expect(page).to have_selector("#sortable_issue_#{@issue.iid} .assignee-icon img", count: 1)
-    end
-  end
-
-  context 'merge requests' do
-    let(:merge_request)        { page.find_by_id('merge_requests-list-unassigned').find('li') }
-    let(:merge_request_target) { page.find_by_id('merge_requests-list-ongoing') }
-
-    it 'does not allow guest to drag merge request' do
-      create_and_drag_merge_request
-
-      expect(merge_request_target).not_to have_selector('.issuable-row')
-    end
-
-    it 'does not allow authorized user to drag merge request' do
-      login_as(user)
-      create_and_drag_merge_request
-
-      expect(merge_request_target).not_to have_selector('.issuable-row')
-    end
-
-    it 'allows author to drag merge request' do
-      login_as(user)
-      create_and_drag_merge_request(author: user)
-
-      expect(merge_request_target).to have_selector('.issuable-row')
-    end
-
-    it 'allows admin to drag merge request' do
-      login_as(:admin)
-      create_and_drag_merge_request
-
-      expect(merge_request_target).to have_selector('.issuable-row')
-    end
-  end
-
-  def create_and_drag_issue(params = {})
-    @issue = create(:issue, params.merge(title: 'Foo', project: project, milestone: milestone))
-
-    visit namespace_project_milestone_path(project.namespace, project, milestone)
-    scroll_into_view('.milestone-content')
-    drag_to(selector: '.issues-sortable-list', list_to_index: 1)
-
-    wait_for_requests
-  end
-
-  def create_and_drag_merge_request(params = {})
-    create(:merge_request, params.merge(title: 'Foo', source_project: project, target_project: project, milestone: milestone))
-
-    visit namespace_project_milestone_path(project.namespace, project, milestone)
-    page.find("a[href='#tab-merge-requests']").click
-
-    wait_for_requests
-
-    scroll_into_view('.milestone-content')
-    drag_to(selector: '.merge_requests-sortable-list', list_to_index: 1)
-
-    wait_for_requests
-  end
-
-  def scroll_into_view(selector)
-    page.evaluate_script("document.querySelector('#{selector}').scrollIntoView();")
-  end
-end
diff --git a/spec/features/milestones/show_spec.rb b/spec/features/milestones/show_spec.rb
index 227eb04ba7282828d58c9f00ac1b897c280abb32..fc7d2f9662dba87b58c07e2c69629c5586f0f55d 100644
--- a/spec/features/milestones/show_spec.rb
+++ b/spec/features/milestones/show_spec.rb
@@ -9,11 +9,11 @@ describe 'Milestone show', feature: true do
 
   before do
     project.add_user(user, :developer) 
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   def visit_milestone
-    visit namespace_project_milestone_path(project.namespace, project, milestone)
+    visit project_milestone_path(project, milestone)
   end
 
   it 'avoids N+1 database queries' do
diff --git a/spec/features/participants_autocomplete_spec.rb b/spec/features/participants_autocomplete_spec.rb
index 449ce80bc718699a80a088d4f3774c466a28fe06..a66d0f4abad193c6052390d40fa21ceb17a64ad0 100644
--- a/spec/features/participants_autocomplete_spec.rb
+++ b/spec/features/participants_autocomplete_spec.rb
@@ -8,7 +8,7 @@ feature 'Member autocomplete', :js do
 
   before do
     note # actually create the note
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   shared_examples "open suggestions when typing @" do
@@ -29,7 +29,7 @@ feature 'Member autocomplete', :js do
   context 'adding a new note on a Issue' do
     let(:noteable) { create(:issue, author: author, project: project) }
     before do
-      visit namespace_project_issue_path(project.namespace, project, noteable)
+      visit project_issue_path(project, noteable)
     end
 
     include_examples "open suggestions when typing @"
@@ -42,7 +42,7 @@ feature 'Member autocomplete', :js do
                              target_project: project, author: author)
     end
     before do
-      visit namespace_project_merge_request_path(project.namespace, project, noteable)
+      visit project_merge_request_path(project, noteable)
     end
 
     include_examples "open suggestions when typing @"
@@ -56,7 +56,7 @@ feature 'Member autocomplete', :js do
     before do
       allow_any_instance_of(Commit).to receive(:author).and_return(author)
 
-      visit namespace_project_commit_path(project.namespace, project, noteable)
+      visit project_commit_path(project, noteable)
     end
 
     include_examples "open suggestions when typing @"
diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb
index 7df628fd7a0b7b4091fad466594113b2350fb839..bb4263d83f37aef54053bc41230a5aab280e947f 100644
--- a/spec/features/profile_spec.rb
+++ b/spec/features/profile_spec.rb
@@ -4,7 +4,7 @@ describe 'Profile account page', feature: true do
   let(:user) { create(:user) }
 
   before do
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   describe 'when signup is enabled' do
diff --git a/spec/features/profiles/account_spec.rb b/spec/features/profiles/account_spec.rb
index 89868c737f7eb896752b0b4e754d92089cd2b525..33fd29b429b59434d78d45ab1af2ed446d088788 100644
--- a/spec/features/profiles/account_spec.rb
+++ b/spec/features/profiles/account_spec.rb
@@ -4,7 +4,7 @@ feature 'Profile > Account', feature: true do
   given(:user) { create(:user, username: 'foo') }
 
   before do
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   describe 'Change username' do
diff --git a/spec/features/profiles/chat_names_spec.rb b/spec/features/profiles/chat_names_spec.rb
index 6f6f7029c0b6891974c8e11874c66762ce46035c..1a162d6be0e3cd2a48eecdab45e9011518614961 100644
--- a/spec/features/profiles/chat_names_spec.rb
+++ b/spec/features/profiles/chat_names_spec.rb
@@ -5,7 +5,7 @@ feature 'Profile > Chat', feature: true do
   given(:service) { create(:service) }
 
   before do
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   describe 'uses authorization link' do
diff --git a/spec/features/profiles/keys_spec.rb b/spec/features/profiles/keys_spec.rb
index 2f436f153aa5c193c45e106c8b15c741150e6e52..13f9afd4ce028d1d963e01716993059a2e5f3fc5 100644
--- a/spec/features/profiles/keys_spec.rb
+++ b/spec/features/profiles/keys_spec.rb
@@ -4,7 +4,7 @@ feature 'Profile > SSH Keys', feature: true do
   let(:user) { create(:user) }
 
   before do
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   describe 'User adds a key' do
diff --git a/spec/features/profiles/oauth_applications_spec.rb b/spec/features/profiles/oauth_applications_spec.rb
index 1a5a9059dbd6067ec4d09e5d3be81241013c8aa4..a6f9beafe174e570a05369d05aa1582e19c862c9 100644
--- a/spec/features/profiles/oauth_applications_spec.rb
+++ b/spec/features/profiles/oauth_applications_spec.rb
@@ -4,7 +4,7 @@ describe 'Profile > Applications', feature: true do
   let(:user) { create(:user) }
 
   before do
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   describe 'User manages applications', js: true do
diff --git a/spec/features/profiles/password_spec.rb b/spec/features/profiles/password_spec.rb
index 4cbdd89d46f02830d5cfc8e1ea5248fe373e4973..86c9df5ff86e77a2852b70e128424fcec9efceee 100644
--- a/spec/features/profiles/password_spec.rb
+++ b/spec/features/profiles/password_spec.rb
@@ -4,7 +4,7 @@ describe 'Profile > Password', feature: true do
   let(:user) { create(:user, password_automatically_set: true) }
 
   before do
-    login_as(user)
+    gitlab_sign_in(user)
     visit edit_profile_password_path
   end
 
@@ -25,7 +25,7 @@ describe 'Profile > Password', feature: true do
         end
       end
 
-      it 'does not contains the current password field after an error' do
+      it 'does not contain the current password field after an error' do
         fill_passwords('mypassword', 'mypassword2')
 
         expect(page).to have_no_field('user[current_password]')
diff --git a/spec/features/profiles/personal_access_tokens_spec.rb b/spec/features/profiles/personal_access_tokens_spec.rb
index 7e2e685df26ceafaf8d13337a6903c5b3cb37126..d7acaaf1eb85c0d4ca5649850b17d710ebb4b77b 100644
--- a/spec/features/profiles/personal_access_tokens_spec.rb
+++ b/spec/features/profiles/personal_access_tokens_spec.rb
@@ -23,7 +23,7 @@ describe 'Profile > Personal Access Tokens', feature: true, js: true do
   end
 
   before do
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   describe "token creation" do
diff --git a/spec/features/profiles/preferences_spec.rb b/spec/features/profiles/preferences_spec.rb
index d368bc4d7533b611ef6ad26a69de38f706632946..8e7ef6bc1101b9d0bf872b2eb8d1d0f72657c825 100644
--- a/spec/features/profiles/preferences_spec.rb
+++ b/spec/features/profiles/preferences_spec.rb
@@ -4,7 +4,7 @@ describe 'Profile > Preferences', feature: true do
   let(:user) { create(:user) }
 
   before do
-    login_as(user)
+    gitlab_sign_in(user)
     visit profile_preferences_path
   end
 
diff --git a/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb b/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb
index e05fbb3715c07d36acd1c16cd7e9eea9c4f07594..c0092836e3ba10a8e5639157ca15740f28f90213 100644
--- a/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb
+++ b/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb
@@ -4,7 +4,7 @@ feature 'Profile > Notifications > User changes notified_of_own_activity setting
   let(:user) { create(:user) }
 
   before do
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   scenario 'User opts into receiving notifications about their own activity' do
diff --git a/spec/features/profiles/user_visits_notifications_tab_spec.rb b/spec/features/profiles/user_visits_notifications_tab_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e98cec79d87a40f099b9510c1e33a88dff516097
--- /dev/null
+++ b/spec/features/profiles/user_visits_notifications_tab_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+feature 'User visits the notifications tab', js: true do
+  let(:project) { create(:empty_project) }
+  let(:user) { create(:user) }
+
+  before do
+    project.team << [user, :master]
+    sign_in(user)
+    visit(profile_notifications_path)
+  end
+
+  it 'changes the project notifications setting' do
+    expect(page).to have_content('Notifications')
+
+    first('#notifications-button').trigger('click')
+    click_link('On mention')
+
+    expect(page).to have_content('On mention')
+  end
+end
diff --git a/spec/features/projects/activity/rss_spec.rb b/spec/features/projects/activity/rss_spec.rb
index 3c1de5c09b20248e3a8d4d66b5ec18ffbb5010c4..97925bc2ebf043789be77c32b6822e570962a1e4 100644
--- a/spec/features/projects/activity/rss_spec.rb
+++ b/spec/features/projects/activity/rss_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 feature 'Project Activity RSS' do
   let(:project) { create(:empty_project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
-  let(:path) { activity_namespace_project_path(project.namespace, project) }
+  let(:path) { activity_project_path(project) }
 
   before do
     create(:issue, project: project)
@@ -12,7 +12,7 @@ feature 'Project Activity RSS' do
     before do
       user = create(:user)
       project.team << [user, :developer]
-      login_as(user)
+      gitlab_sign_in(user)
       visit path
     end
 
diff --git a/spec/features/projects/artifacts/browse_spec.rb b/spec/features/projects/artifacts/browse_spec.rb
index 68375956273491ba4c53ac0c490c013c9f437c33..a34c0c4cecdd4015e39504dd742734646da29a3c 100644
--- a/spec/features/projects/artifacts/browse_spec.rb
+++ b/spec/features/projects/artifacts/browse_spec.rb
@@ -6,7 +6,7 @@ feature 'Browse artifact', :js, feature: true do
   let(:job) { create(:ci_build, :artifacts, pipeline: pipeline) }
 
   def browse_path(path)
-    browse_namespace_project_job_artifacts_path(project.namespace, project, job, path)
+    browse_project_job_artifacts_path(project, job, path)
   end
 
   context 'when visiting old URL' do
diff --git a/spec/features/projects/artifacts/download_spec.rb b/spec/features/projects/artifacts/download_spec.rb
index dd9454840ee577bfd72503ab1f52375fefebf2c1..b76f2be880e01c3c9c77129fab758067a47b5270 100644
--- a/spec/features/projects/artifacts/download_spec.rb
+++ b/spec/features/projects/artifacts/download_spec.rb
@@ -22,7 +22,7 @@ feature 'Download artifact', :js, feature: true do
 
     context 'via job id' do
       let(:download_url) do
-        download_namespace_project_job_artifacts_path(project.namespace, project, job)
+        download_project_job_artifacts_path(project, job)
       end
 
       it_behaves_like 'downloading'
@@ -30,7 +30,7 @@ feature 'Download artifact', :js, feature: true do
 
     context 'via branch name and job name' do
       let(:download_url) do
-        latest_succeeded_namespace_project_artifacts_path(project.namespace, project, "#{pipeline.ref}/download", job: job.name)
+        latest_succeeded_project_artifacts_path(project, "#{pipeline.ref}/download", job: job.name)
       end
 
       it_behaves_like 'downloading'
@@ -44,7 +44,7 @@ feature 'Download artifact', :js, feature: true do
 
     context 'via job id' do
       let(:download_url) do
-        download_namespace_project_job_artifacts_path(project.namespace, project, job)
+        download_project_job_artifacts_path(project, job)
       end
 
       it_behaves_like 'downloading'
@@ -52,7 +52,7 @@ feature 'Download artifact', :js, feature: true do
 
     context 'via branch name and job name' do
       let(:download_url) do
-        latest_succeeded_namespace_project_artifacts_path(project.namespace, project, "#{pipeline.ref}/download", job: job.name)
+        latest_succeeded_project_artifacts_path(project, "#{pipeline.ref}/download", job: job.name)
       end
 
       it_behaves_like 'downloading'
diff --git a/spec/features/projects/artifacts/file_spec.rb b/spec/features/projects/artifacts/file_spec.rb
index 860373e531bf5a44094d3a3c0ce7f4d0cbbd60b6..6d48470ca3a93ad4b08d292021e902cf3d1113a2 100644
--- a/spec/features/projects/artifacts/file_spec.rb
+++ b/spec/features/projects/artifacts/file_spec.rb
@@ -10,7 +10,7 @@ feature 'Artifact file', :js, feature: true do
   end
 
   def file_path(path)
-    file_namespace_project_job_artifacts_path(project.namespace, project, build, path)
+    file_project_job_artifacts_path(project, build, path)
   end
 
   context 'Text file' do
diff --git a/spec/features/projects/artifacts/raw_spec.rb b/spec/features/projects/artifacts/raw_spec.rb
index b589701729de272310e2ce1f7f7cad103139dcb6..3f38d720a0f961d9047eaf95f7ab4d56421072c5 100644
--- a/spec/features/projects/artifacts/raw_spec.rb
+++ b/spec/features/projects/artifacts/raw_spec.rb
@@ -6,7 +6,7 @@ feature 'Raw artifact', :js, feature: true do
   let(:job) { create(:ci_build, :artifacts, pipeline: pipeline) }
 
   def raw_path(path)
-    raw_namespace_project_job_artifacts_path(project.namespace, project, job, path)
+    raw_project_job_artifacts_path(project, job, path)
   end
 
   context 'when visiting old URL' do
diff --git a/spec/features/projects/badges/coverage_spec.rb b/spec/features/projects/badges/coverage_spec.rb
index 01a95bf49acb465ec9d49243214f46bf7a685409..efadb6400962f1a4e5e7e0008e36e4b9c0953253 100644
--- a/spec/features/projects/badges/coverage_spec.rb
+++ b/spec/features/projects/badges/coverage_spec.rb
@@ -7,7 +7,7 @@ feature 'test coverage badge' do
   context 'when user has access to view badge' do
     background do
       project.team << [user, :developer]
-      login_as(user)
+      gitlab_sign_in(user)
     end
 
     scenario 'user requests coverage badge image for pipeline' do
@@ -45,7 +45,7 @@ feature 'test coverage badge' do
   end
 
   context 'when user does not have access to view badge' do
-    background { login_as(user) }
+    background { gitlab_sign_in(user) }
 
     scenario 'user requests test coverage badge image' do
       show_test_coverage_badge
@@ -70,8 +70,7 @@ feature 'test coverage badge' do
   end
 
   def show_test_coverage_badge(job: nil)
-    visit coverage_namespace_project_badges_path(
-      project.namespace, project, ref: :master, job: job, format: :svg)
+    visit coverage_project_badges_path(project, ref: :master, job: job, format: :svg)
   end
 
   def expect_coverage_badge(coverage)
diff --git a/spec/features/projects/badges/list_spec.rb b/spec/features/projects/badges/list_spec.rb
index ae9db0c0d6e7cf0bb4817b1985df927d2d4b4871..cbd44c49d04c399a1fcfbcf07d77517eaff0be89 100644
--- a/spec/features/projects/badges/list_spec.rb
+++ b/spec/features/projects/badges/list_spec.rb
@@ -5,8 +5,8 @@ feature 'list of badges' do
     user = create(:user)
     project = create(:project)
     project.team << [user, :master]
-    login_as(user)
-    visit namespace_project_pipelines_settings_path(project.namespace, project)
+    gitlab_sign_in(user)
+    visit project_pipelines_settings_path(project)
   end
 
   scenario 'user wants to see build status badge' do
diff --git a/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb b/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb
index 53c5a52ce3a44e87d2080b4e43d5c1d1ffd357fb..7564338b3014cf0a1c4eae266c42040c5fe09356 100644
--- a/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb
+++ b/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb
@@ -13,14 +13,14 @@ feature 'Blob button line permalinks (BlobLinePermalinkUpdater)', feature: true,
     end
 
     def visit_blob(fragment = nil)
-      visit namespace_project_blob_path(project.namespace, project, tree_join('master', path), anchor: fragment)
+      visit project_blob_path(project, tree_join('master', path), anchor: fragment)
     end
 
     describe 'Click "Permalink" button' do
       it 'works with no initial line number fragment hash' do
         visit_blob
 
-        expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(namespace_project_blob_path(project.namespace, project, tree_join(sha, path))))
+        expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(project_blob_path(project, tree_join(sha, path))))
       end
 
       it 'maintains intitial fragment hash' do
@@ -28,7 +28,7 @@ feature 'Blob button line permalinks (BlobLinePermalinkUpdater)', feature: true,
 
         visit_blob(fragment)
 
-        expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(namespace_project_blob_path(project.namespace, project, tree_join(sha, path), anchor: fragment)))
+        expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(project_blob_path(project, tree_join(sha, path), anchor: fragment)))
       end
 
       it 'changes fragment hash if line number clicked' do
@@ -39,7 +39,7 @@ feature 'Blob button line permalinks (BlobLinePermalinkUpdater)', feature: true,
         find('#L3').click
         find("##{ending_fragment}").click
 
-        expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(namespace_project_blob_path(project.namespace, project, tree_join(sha, path), anchor: ending_fragment)))
+        expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(project_blob_path(project, tree_join(sha, path), anchor: ending_fragment)))
       end
 
       it 'with initial fragment hash, changes fragment hash if line number clicked' do
@@ -51,15 +51,15 @@ feature 'Blob button line permalinks (BlobLinePermalinkUpdater)', feature: true,
         find('#L3').click
         find("##{ending_fragment}").click
 
-        expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(namespace_project_blob_path(project.namespace, project, tree_join(sha, path), anchor: ending_fragment)))
+        expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(project_blob_path(project, tree_join(sha, path), anchor: ending_fragment)))
       end
     end
 
-    describe 'Click "Annotate" button' do
+    describe 'Click "Blame" button' do
       it 'works with no initial line number fragment hash' do
         visit_blob
 
-        expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(namespace_project_blame_path(project.namespace, project, tree_join('master', path))))
+        expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(project_blame_path(project, tree_join('master', path))))
       end
 
       it 'maintains intitial fragment hash' do
@@ -67,7 +67,7 @@ feature 'Blob button line permalinks (BlobLinePermalinkUpdater)', feature: true,
 
         visit_blob(fragment)
 
-        expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(namespace_project_blame_path(project.namespace, project, tree_join('master', path), anchor: fragment)))
+        expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(project_blame_path(project, tree_join('master', path), anchor: fragment)))
       end
 
       it 'changes fragment hash if line number clicked' do
@@ -78,7 +78,7 @@ feature 'Blob button line permalinks (BlobLinePermalinkUpdater)', feature: true,
         find('#L3').click
         find("##{ending_fragment}").click
 
-        expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(namespace_project_blame_path(project.namespace, project, tree_join('master', path), anchor: ending_fragment)))
+        expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(project_blame_path(project, tree_join('master', path), anchor: ending_fragment)))
       end
 
       it 'with initial fragment hash, changes fragment hash if line number clicked' do
@@ -90,7 +90,7 @@ feature 'Blob button line permalinks (BlobLinePermalinkUpdater)', feature: true,
         find('#L3').click
         find("##{ending_fragment}").click
 
-        expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(namespace_project_blame_path(project.namespace, project, tree_join('master', path), anchor: ending_fragment)))
+        expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(project_blame_path(project, tree_join('master', path), anchor: ending_fragment)))
       end
     end
   end
diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb
index 71ffa352f80489d680404b487f7647611f1d7fd5..3427f639930713b088a417f7b4cb627d9fa749f4 100644
--- a/spec/features/projects/blobs/blob_show_spec.rb
+++ b/spec/features/projects/blobs/blob_show_spec.rb
@@ -4,7 +4,7 @@ feature 'File blob', :js, feature: true do
   let(:project) { create(:project, :public) }
 
   def visit_blob(path, anchor: nil, ref: 'master')
-    visit namespace_project_blob_path(project.namespace, project, File.join(ref, path), anchor: anchor)
+    visit project_blob_path(project, File.join(ref, path), anchor: anchor)
 
     wait_for_requests
   end
diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb
index d04c3248ead15c8e46dd068f62e9fe25fd6097f8..c4e53293be0bfdcaac8cb892a3ebda43d98c5727 100644
--- a/spec/features/projects/blobs/edit_spec.rb
+++ b/spec/features/projects/blobs/edit_spec.rb
@@ -14,7 +14,7 @@ feature 'Editing file blob', feature: true, js: true do
 
     before do
       project.team << [user, role]
-      login_as(user)
+      gitlab_sign_in(user)
     end
 
     def edit_and_commit
@@ -26,7 +26,7 @@ feature 'Editing file blob', feature: true, js: true do
 
     context 'from MR diff' do
       before do
-        visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
+        visit diffs_project_merge_request_path(project, merge_request)
         edit_and_commit
       end
 
@@ -37,7 +37,7 @@ feature 'Editing file blob', feature: true, js: true do
 
     context 'from blob file path' do
       before do
-        visit namespace_project_blob_path(project.namespace, project, tree_join(branch, file_path))
+        visit project_blob_path(project, tree_join(branch, file_path))
         edit_and_commit
       end
 
@@ -55,15 +55,15 @@ feature 'Editing file blob', feature: true, js: true do
 
         before do
           project.team << [user, :developer]
-          visit namespace_project_edit_blob_path(project.namespace, project, tree_join(branch, file_path))
+          visit project_edit_blob_path(project, tree_join(branch, file_path))
         end
 
         it 'redirects to sign in and returns' do
           expect(page).to have_current_path(new_user_session_path)
 
-          login_as(user)
+          gitlab_sign_in(user)
 
-          expect(page).to have_current_path(namespace_project_edit_blob_path(project.namespace, project, tree_join(branch, file_path)))
+          expect(page).to have_current_path(project_edit_blob_path(project, tree_join(branch, file_path)))
         end
       end
 
@@ -71,15 +71,15 @@ feature 'Editing file blob', feature: true, js: true do
         let(:user) { create(:user) }
 
         before do
-          visit namespace_project_edit_blob_path(project.namespace, project, tree_join(branch, file_path))
+          visit project_edit_blob_path(project, tree_join(branch, file_path))
         end
 
         it 'redirects to sign in and returns' do
           expect(page).to have_current_path(new_user_session_path)
 
-          login_as(user)
+          gitlab_sign_in(user)
 
-          expect(page).to have_current_path(namespace_project_blob_path(project.namespace, project, tree_join(branch, file_path)))
+          expect(page).to have_current_path(project_blob_path(project, tree_join(branch, file_path)))
         end
       end
     end
@@ -92,23 +92,23 @@ feature 'Editing file blob', feature: true, js: true do
         project.team << [user, :developer]
         project.repository.add_branch(user, protected_branch, 'master')
         create(:protected_branch, project: project, name: protected_branch)
-        login_as(user)
+        gitlab_sign_in(user)
       end
 
       context 'on some branch' do
         before do
-          visit namespace_project_edit_blob_path(project.namespace, project, tree_join(branch, file_path))
+          visit project_edit_blob_path(project, tree_join(branch, file_path))
         end
 
         it 'shows blob editor with same branch' do
-          expect(page).to have_current_path(namespace_project_edit_blob_path(project.namespace, project, tree_join(branch, file_path)))
+          expect(page).to have_current_path(project_edit_blob_path(project, tree_join(branch, file_path)))
           expect(find('.js-branch-name').value).to eq(branch)
         end
       end
 
       context 'with protected branch' do
         before do
-          visit namespace_project_edit_blob_path(project.namespace, project, tree_join(protected_branch, file_path))
+          visit project_edit_blob_path(project, tree_join(protected_branch, file_path))
         end
 
         it 'shows blob editor with patch branch' do
@@ -122,12 +122,12 @@ feature 'Editing file blob', feature: true, js: true do
 
       before do
         project.team << [user, :master]
-        login_as(user)
-        visit namespace_project_edit_blob_path(project.namespace, project, tree_join(branch, file_path))
+        gitlab_sign_in(user)
+        visit project_edit_blob_path(project, tree_join(branch, file_path))
       end
 
       it 'shows blob editor with same branch' do
-        expect(page).to have_current_path(namespace_project_edit_blob_path(project.namespace, project, tree_join(branch, file_path)))
+        expect(page).to have_current_path(project_edit_blob_path(project, tree_join(branch, file_path)))
         expect(find('.js-branch-name').value).to eq(branch)
       end
     end
diff --git a/spec/features/projects/blobs/shortcuts_blob_spec.rb b/spec/features/projects/blobs/shortcuts_blob_spec.rb
index 30e2d5872679f1d74216a86285597c1d977a29c7..9cacda843783a259dcd58a717c02f8d3ec99e734 100644
--- a/spec/features/projects/blobs/shortcuts_blob_spec.rb
+++ b/spec/features/projects/blobs/shortcuts_blob_spec.rb
@@ -12,7 +12,7 @@ feature 'Blob shortcuts', feature: true do
     end
 
     def visit_blob(fragment = nil)
-      visit namespace_project_blob_path(project.namespace, project, tree_join('master', path), anchor: fragment)
+      visit project_blob_path(project, tree_join('master', path), anchor: fragment)
     end
 
     describe 'pressing "y"' do
@@ -21,7 +21,7 @@ feature 'Blob shortcuts', feature: true do
 
         find('body').native.send_key('y')
 
-        expect(page).to have_current_path(get_absolute_url(namespace_project_blob_path(project.namespace, project, tree_join(sha, path))), url: true)
+        expect(page).to have_current_path(get_absolute_url(project_blob_path(project, tree_join(sha, path))), url: true)
       end
 
       it 'maintains fragment hash when redirecting' do
@@ -30,7 +30,7 @@ feature 'Blob shortcuts', feature: true do
 
         find('body').native.send_key('y')
 
-        expect(page).to have_current_path(get_absolute_url(namespace_project_blob_path(project.namespace, project, tree_join(sha, path), anchor: fragment)), url: true)
+        expect(page).to have_current_path(get_absolute_url(project_blob_path(project, tree_join(sha, path), anchor: fragment)), url: true)
       end
     end
   end
diff --git a/spec/features/projects/branches/download_buttons_spec.rb b/spec/features/projects/branches/download_buttons_spec.rb
index 92028c1936102665d38529103de74ad3c5eda124..52323c21112afeedcacb1df68ccb0eda5b5463e2 100644
--- a/spec/features/projects/branches/download_buttons_spec.rb
+++ b/spec/features/projects/branches/download_buttons_spec.rb
@@ -22,20 +22,18 @@ feature 'Download buttons in branches page', feature: true do
   end
 
   background do
-    login_as(user)
+    gitlab_sign_in(user)
     project.team << [user, role]
   end
 
   describe 'when checking branches' do
     context 'with artifacts' do
       before do
-        visit namespace_project_branches_path(project.namespace, project)
+        visit project_branches_path(project)
       end
 
       scenario 'shows download artifacts button' do
-        href = latest_succeeded_namespace_project_artifacts_path(
-          project.namespace, project, 'binary-encoding/download',
-          job: 'build')
+        href = latest_succeeded_project_artifacts_path(project, 'binary-encoding/download', job: 'build')
 
         expect(page).to have_link "Download '#{build.name}'", href: href
       end
diff --git a/spec/features/projects/branches/new_branch_ref_dropdown_spec.rb b/spec/features/projects/branches/new_branch_ref_dropdown_spec.rb
index c5e0a0f051710b5f20c2a5741ca2d6a1ececbe4e..ab9af8fa603d7fbcd4995f694588b3bde2c83a84 100644
--- a/spec/features/projects/branches/new_branch_ref_dropdown_spec.rb
+++ b/spec/features/projects/branches/new_branch_ref_dropdown_spec.rb
@@ -8,8 +8,8 @@ describe 'New Branch Ref Dropdown', :js, :feature do
   before do
     project.add_master(user)
 
-    login_as(user)
-    visit new_namespace_project_branch_path(project.namespace, project)
+    gitlab_sign_in(user)
+    visit new_project_branch_path(project)
   end
 
   it 'filters a list of branches and tags' do
diff --git a/spec/features/projects/branches_spec.rb b/spec/features/projects/branches_spec.rb
index 7668ce5f8beb349fc22eae2d9befa907c1526fcf..4fae324d8d5edf39ef9af399a6bf0c6d42e5deea 100644
--- a/spec/features/projects/branches_spec.rb
+++ b/spec/features/projects/branches_spec.rb
@@ -1,6 +1,7 @@
 require 'spec_helper'
 
 describe 'Branches', feature: true do
+  let(:user) { create(:user) }
   let(:project) { create(:project, :public) }
   let(:repository) { project.repository }
 
@@ -12,30 +13,68 @@ describe 'Branches', feature: true do
 
   context 'logged in as developer' do
     before do
-      login_as :user
-      project.team << [@user, :developer]
+      sign_in(user)
+      project.team << [user, :developer]
     end
 
     describe 'Initial branches page' do
       it 'shows all the branches' do
-        visit namespace_project_branches_path(project.namespace, project)
+        visit project_branches_path(project)
 
-        repository.branches { |branch| expect(page).to have_content("#{branch.name}") }
+        repository.branches_sorted_by(:name).first(20).each do |branch|
+          expect(page).to have_content("#{branch.name}")
+        end
         expect(page).to have_content("Protected branches can be managed in project settings")
       end
 
+      it 'sorts the branches by name' do
+        visit project_branches_path(project)
+
+        click_button "Name" # Open sorting dropdown
+        click_link "Name"
+
+        sorted = repository.branches_sorted_by(:name).first(20).map do |branch|
+          Regexp.escape(branch.name)
+        end
+        expect(page).to have_content(/#{sorted.join(".*")}/)
+      end
+
+      it 'sorts the branches by last updated' do
+        visit project_branches_path(project)
+
+        click_button "Name" # Open sorting dropdown
+        click_link "Last updated"
+
+        sorted = repository.branches_sorted_by(:updated_desc).first(20).map do |branch|
+          Regexp.escape(branch.name)
+        end
+        expect(page).to have_content(/#{sorted.join(".*")}/)
+      end
+
+      it 'sorts the branches by oldest updated' do
+        visit project_branches_path(project)
+
+        click_button "Name" # Open sorting dropdown
+        click_link "Oldest updated"
+
+        sorted = repository.branches_sorted_by(:updated_asc).first(20).map do |branch|
+          Regexp.escape(branch.name)
+        end
+        expect(page).to have_content(/#{sorted.join(".*")}/)
+      end
+
       it 'avoids a N+1 query in branches index' do
-        control_count = ActiveRecord::QueryRecorder.new { visit namespace_project_branches_path(project.namespace, project) }.count
+        control_count = ActiveRecord::QueryRecorder.new { visit project_branches_path(project) }.count
 
-        %w(one two three four five).each { |ref| repository.add_branch(@user, ref, 'master') }
+        %w(one two three four five).each { |ref| repository.add_branch(user, ref, 'master') }
 
-        expect { visit namespace_project_branches_path(project.namespace, project) }.not_to exceed_query_limit(control_count)
+        expect { visit project_branches_path(project) }.not_to exceed_query_limit(control_count)
       end
     end
 
     describe 'Find branches' do
       it 'shows filtered branches', js: true do
-        visit namespace_project_branches_path(project.namespace, project)
+        visit project_branches_path(project)
 
         fill_in 'branch-search', with: 'fix'
         find('#branch-search').native.send_keys(:enter)
@@ -47,7 +86,7 @@ describe 'Branches', feature: true do
 
     describe 'Delete unprotected branch' do
       it 'removes branch after confirmation', js: true do
-        visit namespace_project_branches_path(project.namespace, project)
+        visit project_branches_path(project)
 
         fill_in 'branch-search', with: 'fix'
 
@@ -64,18 +103,18 @@ describe 'Branches', feature: true do
 
     describe 'Delete protected branch' do
       before do
-        project.add_user(@user, :master)
-        visit namespace_project_protected_branches_path(project.namespace, project)
+        project.add_user(user, :master)
+        visit project_protected_branches_path(project)
         set_protected_branch_name('fix')
         click_on "Protect"
 
         within(".protected-branches-list") { expect(page).to have_content('fix') }
         expect(ProtectedBranch.count).to eq(1)
-        project.add_user(@user, :developer)
+        project.add_user(user, :developer)
       end
 
       it 'does not allow devleoper to removes protected branch', js: true do
-        visit namespace_project_branches_path(project.namespace, project)
+        visit project_branches_path(project)
 
         fill_in 'branch-search', with: 'fix'
         find('#branch-search').native.send_keys(:enter)
@@ -87,13 +126,13 @@ describe 'Branches', feature: true do
 
   context 'logged in as master' do
     before do
-      login_as :user
-      project.team << [@user, :master]
+      sign_in(user)
+      project.team << [user, :master]
     end
 
     describe 'Delete protected branch' do
       before do
-        visit namespace_project_protected_branches_path(project.namespace, project)
+        visit project_protected_branches_path(project)
         set_protected_branch_name('fix')
         click_on "Protect"
 
@@ -102,7 +141,7 @@ describe 'Branches', feature: true do
       end
 
       it 'removes branch after modal confirmation', js: true do
-        visit namespace_project_branches_path(project.namespace, project)
+        visit project_branches_path(project)
 
         fill_in 'branch-search', with: 'fix'
         find('#branch-search').native.send_keys(:enter)
@@ -125,7 +164,7 @@ describe 'Branches', feature: true do
 
   context 'logged out' do
     before do
-      visit namespace_project_branches_path(project.namespace, project)
+      visit project_branches_path(project)
     end
 
     it 'does not show merge request button' do
diff --git a/spec/features/projects/commit/builds_spec.rb b/spec/features/projects/commit/builds_spec.rb
index 268d420c594bad4189874569c9aa2db99b8f35be..69eeb8e285e76205f9625366cca060c696484f70 100644
--- a/spec/features/projects/commit/builds_spec.rb
+++ b/spec/features/projects/commit/builds_spec.rb
@@ -6,7 +6,7 @@ feature 'project commit pipelines', js: true do
   background do
     user = create(:user)
     project.team << [user, :master]
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   context 'when no builds triggered yet' do
@@ -17,7 +17,7 @@ feature 'project commit pipelines', js: true do
     end
 
     scenario 'user views commit pipelines page' do
-      visit pipelines_namespace_project_commit_path(project.namespace, project, project.commit.sha)
+      visit pipelines_project_commit_path(project, project.commit.sha)
 
       page.within('.table-holder') do
         expect(page).to have_content project.pipelines[0].status # pipeline status
diff --git a/spec/features/projects/commit/cherry_pick_spec.rb b/spec/features/projects/commit/cherry_pick_spec.rb
index bc7ca0ddd389ff29c3054eeace09cd6c70276758..2d18add82b50148f8d9f6843f4557b679d6be591 100644
--- a/spec/features/projects/commit/cherry_pick_spec.rb
+++ b/spec/features/projects/commit/cherry_pick_spec.rb
@@ -1,15 +1,16 @@
 require 'spec_helper'
 
 describe 'Cherry-pick Commits' do
+  let(:user) { create(:user) }
   let(:group) { create(:group) }
   let(:project) { create(:project, namespace: group) }
   let(:master_pickable_commit)  { project.commit('7d3b0f7cff5f37573aea97cebfd5692ea1689924') }
   let(:master_pickable_merge)  { project.commit('e56497bb5f03a90a51293fc6d516788730953899') }
 
   before do
-    login_as :user
-    project.team << [@user, :master]
-    visit namespace_project_commit_path(project.namespace, project, master_pickable_commit.id)
+    sign_in(user)
+    project.team << [user, :master]
+    visit project_commit_path(project, master_pickable_commit.id)
   end
 
   context "I cherry-pick a commit" do
@@ -42,7 +43,7 @@ describe 'Cherry-pick Commits' do
         uncheck 'create_merge_request'
         click_button 'Cherry-pick'
       end
-      visit namespace_project_commit_path(project.namespace, project, master_pickable_commit.id)
+      visit project_commit_path(project, master_pickable_commit.id)
       find("a[href='#modal-cherry-pick-commit']").click
       page.within('#modal-cherry-pick-commit') do
         uncheck 'create_merge_request'
diff --git a/spec/features/projects/commit/mini_pipeline_graph_spec.rb b/spec/features/projects/commit/mini_pipeline_graph_spec.rb
index f2de195eb7fc5ba2512ce5ce01211d99b30f9cb7..c8222326e919c42ae67c40fa933b4f02090f5b2e 100644
--- a/spec/features/projects/commit/mini_pipeline_graph_spec.rb
+++ b/spec/features/projects/commit/mini_pipeline_graph_spec.rb
@@ -5,7 +5,7 @@ feature 'Mini Pipeline Graph in Commit View', :js, :feature do
   let(:project) { create(:project, :public) }
 
   before do
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   context 'when commit has pipelines' do
@@ -22,7 +22,7 @@ feature 'Mini Pipeline Graph in Commit View', :js, :feature do
 
     before do
       build.run
-      visit namespace_project_commit_path(project.namespace, project, project.commit.id)
+      visit project_commit_path(project, project.commit.id)
     end
 
     it 'should display a mini pipeline graph' do
@@ -43,7 +43,7 @@ feature 'Mini Pipeline Graph in Commit View', :js, :feature do
 
   context 'when commit does not have pipelines' do
     before do
-      visit namespace_project_commit_path(project.namespace, project, project.commit.id)
+      visit project_commit_path(project, project.commit.id)
     end
 
     it 'should not display a mini pipeline graph' do
diff --git a/spec/features/projects/commit/rss_spec.rb b/spec/features/projects/commit/rss_spec.rb
index 03b6d560c96f8b9cb61fe0f2627097d69adcff58..152c0d7c8de0c0632f1ea71916d8da5256b3bef0 100644
--- a/spec/features/projects/commit/rss_spec.rb
+++ b/spec/features/projects/commit/rss_spec.rb
@@ -2,13 +2,13 @@ require 'spec_helper'
 
 feature 'Project Commits RSS' do
   let(:project) { create(:project, :repository, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
-  let(:path) { namespace_project_commits_path(project.namespace, project, :master) }
+  let(:path) { project_commits_path(project, :master) }
 
   context 'when signed in' do
     before do
       user = create(:user)
       project.team << [user, :developer]
-      login_as(user)
+      gitlab_sign_in(user)
       visit path
     end
 
diff --git a/spec/features/projects/compare_spec.rb b/spec/features/projects/compare_spec.rb
index ee6985ad993dc81552193d5b6c927e113cfbe422..c3adfa87c444aa5806fffc1ef8858e7f07549475 100644
--- a/spec/features/projects/compare_spec.rb
+++ b/spec/features/projects/compare_spec.rb
@@ -6,8 +6,8 @@ describe "Compare", js: true do
 
   before do
     project.team << [user, :master]
-    login_as user
-    visit namespace_project_compare_index_path(project.namespace, project, from: "master", to: "master")
+    gitlab_sign_in user
+    visit project_compare_index_path(project, from: "master", to: "master")
   end
 
   describe "branches" do
diff --git a/spec/features/projects/deploy_keys_spec.rb b/spec/features/projects/deploy_keys_spec.rb
index 06abfbbc86b78ce11958ea0389861f73b0dd5ff5..a310d14be1037015bddecd6f72a2dfaa2d2915d6 100644
--- a/spec/features/projects/deploy_keys_spec.rb
+++ b/spec/features/projects/deploy_keys_spec.rb
@@ -6,7 +6,7 @@ describe 'Project deploy keys', :js, :feature do
 
   before do
     project.team << [user, :master]
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   describe 'removing key' do
@@ -15,7 +15,7 @@ describe 'Project deploy keys', :js, :feature do
     end
 
     it 'removes association between project and deploy key' do
-      visit namespace_project_settings_repository_path(project.namespace, project)
+      visit project_settings_repository_path(project)
 
       page.within(find('.deploy-keys')) do
         expect(page).to have_selector('.deploy-keys li', count: 1)
diff --git a/spec/features/projects/developer_views_empty_project_instructions_spec.rb b/spec/features/projects/developer_views_empty_project_instructions_spec.rb
index 0c51fe72ca4e4b97a6c77f65038c9c60ab64f2dd..290dc1a2f79091340ab85f02b9a94974fc47f496 100644
--- a/spec/features/projects/developer_views_empty_project_instructions_spec.rb
+++ b/spec/features/projects/developer_views_empty_project_instructions_spec.rb
@@ -7,7 +7,7 @@ feature 'Developer views empty project instructions', feature: true do
   background do
     project.team << [developer, :developer]
 
-    login_as(developer)
+    gitlab_sign_in(developer)
   end
 
   context 'without an SSH key' do
@@ -47,7 +47,7 @@ feature 'Developer views empty project instructions', feature: true do
   end
 
   def visit_project
-    visit namespace_project_path(project.namespace, project)
+    visit project_path(project)
   end
 
   def select_protocol(protocol)
diff --git a/spec/features/projects/diffs/diff_show_spec.rb b/spec/features/projects/diffs/diff_show_spec.rb
index 48b7f1e0f34a65c72f507a24ccc6a1b56e041e23..b528b28349533c031fc49d6d443f01ddd549a7a8 100644
--- a/spec/features/projects/diffs/diff_show_spec.rb
+++ b/spec/features/projects/diffs/diff_show_spec.rb
@@ -4,7 +4,7 @@ feature 'Diff file viewer', :js, feature: true do
   let(:project) { create(:project, :public, :repository) }
 
   def visit_commit(sha, anchor: nil)
-    visit namespace_project_commit_path(project.namespace, project, sha, anchor: anchor)
+    visit project_commit_path(project, sha, anchor: anchor)
 
     wait_for_requests
   end
diff --git a/spec/features/projects/edit_spec.rb b/spec/features/projects/edit_spec.rb
index a263781c43c96a2da51b25281a0c282908c64111..78c1a1f1d1ae22e1c92df950086d518e22acdb2b 100644
--- a/spec/features/projects/edit_spec.rb
+++ b/spec/features/projects/edit_spec.rb
@@ -6,9 +6,9 @@ feature 'Project edit', feature: true, js: true do
 
   before do
     project.team << [user, :master]
-    login_as(user)
+    gitlab_sign_in(user)
 
-    visit edit_namespace_project_path(project.namespace, project)
+    visit edit_project_path(project)
   end
 
   context 'feature visibility' do
diff --git a/spec/features/projects/environments/environment_metrics_spec.rb b/spec/features/projects/environments/environment_metrics_spec.rb
index ee925e811e1b6d3a71d811c9bda15809eb5e3f10..841514ac7070faeaba3fd7b9038c0064a32da487 100644
--- a/spec/features/projects/environments/environment_metrics_spec.rb
+++ b/spec/features/projects/environments/environment_metrics_spec.rb
@@ -15,7 +15,7 @@ feature 'Environment > Metrics', :feature do
     create(:deployment, environment: environment, deployable: build)
     stub_all_prometheus_requests(environment.slug)
 
-    login_as(user)
+    gitlab_sign_in(user)
     visit_environment(environment)
   end
 
@@ -27,13 +27,11 @@ feature 'Environment > Metrics', :feature do
     scenario 'shows metrics' do
       click_link('See metrics')
 
-      expect(page).to have_css('svg.prometheus-graph')
+      expect(page).to have_css('div#prometheus-graphs')
     end
   end
 
   def visit_environment(environment)
-    visit namespace_project_environment_path(environment.project.namespace,
-                                             environment.project,
-                                             environment)
+    visit project_environment_path(environment.project, environment)
   end
 end
diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb
index 18b608c863efe50bdfe0516692986d6d86af835e..e3f40f8e661692fe6cf154120d7e1e0f0baf3d21 100644
--- a/spec/features/projects/environments/environment_spec.rb
+++ b/spec/features/projects/environments/environment_spec.rb
@@ -6,7 +6,7 @@ feature 'Environment', :feature do
   given(:role) { :developer }
 
   background do
-    login_as(user)
+    gitlab_sign_in(user)
     project.team << [user, role]
   end
 
@@ -114,7 +114,7 @@ feature 'Environment', :feature do
                 before do
                   # Stub #terminals as it causes js-enabled feature specs to render the page incorrectly
                   allow_any_instance_of(Environment).to receive(:terminals) { nil }
-                  visit terminal_namespace_project_environment_path(project.namespace, project, environment)
+                  visit terminal_project_environment_path(project, environment)
                 end
 
                 it 'displays a web terminal' do
@@ -194,9 +194,7 @@ feature 'Environment', :feature do
                              name: 'staging-1.0/review',
                              state: :available)
 
-        visit folder_namespace_project_environments_path(project.namespace,
-                                                         project,
-                                                         id: 'staging-1.0')
+        visit folder_project_environments_path(project, id: 'staging-1.0')
       end
 
       it 'renders a correct environment folder' do
@@ -221,7 +219,7 @@ feature 'Environment', :feature do
     end
 
     scenario 'user deletes the branch with running environment' do
-      visit namespace_project_branches_path(project.namespace, project, search: 'feature')
+      visit project_branches_path(project, search: 'feature')
 
       remove_branch_with_hooks(project, user, 'feature') do
         page.within('.js-branch-feature') { find('a.btn-remove').click }
@@ -249,12 +247,10 @@ feature 'Environment', :feature do
   end
 
   def visit_environment(environment)
-    visit namespace_project_environment_path(environment.project.namespace,
-                                             environment.project,
-                                             environment)
+    visit project_environment_path(environment.project, environment)
   end
 
   def have_terminal_button
-    have_link(nil, href: terminal_namespace_project_environment_path(project.namespace, project, environment))
+    have_link(nil, href: terminal_project_environment_path(project, environment))
   end
 end
diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb
index 613b1edba36eca19c36d673ed5ae0192db39ebf0..af3af3eb965b73fd271431cef757d88f17b3722b 100644
--- a/spec/features/projects/environments/environments_spec.rb
+++ b/spec/features/projects/environments/environments_spec.rb
@@ -7,7 +7,7 @@ feature 'Environments page', :feature, :js do
 
   background do
     project.team << [user, role]
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   given!(:environment) { }
@@ -29,7 +29,7 @@ feature 'Environments page', :feature, :js do
 
       describe 'in available tab page' do
         it 'should show one environment' do
-          visit namespace_project_environments_path(project.namespace, project, scope: 'available')
+          visit project_environments_path(project, scope: 'available')
           expect(page).to have_css('.environments-container')
           expect(page.all('.environment-name').length).to eq(1)
         end
@@ -37,7 +37,7 @@ feature 'Environments page', :feature, :js do
 
       describe 'in stopped tab page' do
         it 'should show no environments' do
-          visit namespace_project_environments_path(project.namespace, project, scope: 'stopped')
+          visit project_environments_path(project, scope: 'stopped')
           expect(page).to have_css('.environments-container')
           expect(page).to have_content('You don\'t have any environments right now')
         end
@@ -49,7 +49,7 @@ feature 'Environments page', :feature, :js do
 
       describe 'in available tab page' do
         it 'should show no environments' do
-          visit namespace_project_environments_path(project.namespace, project, scope: 'available')
+          visit project_environments_path(project, scope: 'available')
           expect(page).to have_css('.environments-container')
           expect(page).to have_content('You don\'t have any environments right now')
         end
@@ -57,7 +57,7 @@ feature 'Environments page', :feature, :js do
 
       describe 'in stopped tab page' do
         it 'should show one environment' do
-          visit namespace_project_environments_path(project.namespace, project, scope: 'stopped')
+          visit project_environments_path(project, scope: 'stopped')
           expect(page).to have_css('.environments-container')
           expect(page.all('.environment-name').length).to eq(1)
         end
@@ -151,7 +151,7 @@ feature 'Environments page', :feature, :js do
           find('.js-dropdown-play-icon-container').click
           expect(page).to have_content(action.name.humanize)
 
-          expect { find('.js-manual-action-link').click }
+          expect { find('.js-manual-action-link').trigger('click') }
             .not_to change { Ci::Pipeline.count }
         end
 
@@ -277,10 +277,10 @@ feature 'Environments page', :feature, :js do
   end
 
   def have_terminal_button
-    have_link(nil, href: terminal_namespace_project_environment_path(project.namespace, project, environment))
+    have_link(nil, href: terminal_project_environment_path(project, environment))
   end
 
   def visit_environments(project)
-    visit namespace_project_environments_path(project.namespace, project)
+    visit project_environments_path(project)
   end
 end
diff --git a/spec/features/projects/features_visibility_spec.rb b/spec/features/projects/features_visibility_spec.rb
index d76b5e4ef1b0dbb6d5d3535e218169a5c1767cb2..45b0c8d1a1839bbe47af37b138464623947b4306 100644
--- a/spec/features/projects/features_visibility_spec.rb
+++ b/spec/features/projects/features_visibility_spec.rb
@@ -9,7 +9,7 @@ describe 'Edit Project Settings', feature: true do
   describe 'project features visibility selectors', js: true do
     before do
       project.team << [member, :master]
-      login_as(member)
+      gitlab_sign_in(member)
     end
 
     tools = { builds: "pipelines", issues: "issues", wiki: "wiki", snippets: "snippets", merge_requests: "merge_requests" }
@@ -17,7 +17,7 @@ describe 'Edit Project Settings', feature: true do
     tools.each do |tool_name, shortcut_name|
       describe "feature #{tool_name}" do
         it 'toggles visibility' do
-          visit edit_namespace_project_path(project.namespace, project)
+          visit edit_project_path(project)
 
           select 'Disabled', from: "project_project_feature_attributes_#{tool_name}_access_level"
           click_button 'Save changes'
@@ -44,7 +44,7 @@ describe 'Edit Project Settings', feature: true do
         project.project_feature.update(issues_access_level: ProjectFeature::DISABLED)
         allow_any_instance_of(Project).to receive(:external_issue_tracker).and_return(JiraService.new)
 
-        visit namespace_project_path(project.namespace, project)
+        visit project_path(project)
 
         expect(page).to have_selector(".shortcuts-issues")
       end
@@ -52,7 +52,7 @@ describe 'Edit Project Settings', feature: true do
 
     context "pipelines subtabs" do
       it "shows builds when enabled" do
-        visit namespace_project_pipelines_path(project.namespace, project)
+        visit project_pipelines_path(project)
 
         expect(page).to have_selector(".shortcuts-builds")
       end
@@ -60,7 +60,7 @@ describe 'Edit Project Settings', feature: true do
       it "hides builds when disabled" do
         allow(Ability).to receive(:allowed?).with(member, :read_builds, project).and_return(false)
 
-        visit namespace_project_pipelines_path(project.namespace, project)
+        visit project_pipelines_path(project)
 
         expect(page).not_to have_selector(".shortcuts-builds")
       end
@@ -73,17 +73,17 @@ describe 'Edit Project Settings', feature: true do
 
     let(:tools) do
       {
-        builds: namespace_project_job_path(project.namespace, project, job),
-        issues: namespace_project_issues_path(project.namespace, project),
-        wiki: namespace_project_wiki_path(project.namespace, project, :home),
-        snippets: namespace_project_snippets_path(project.namespace, project),
-        merge_requests: namespace_project_merge_requests_path(project.namespace, project)
+        builds: project_job_path(project, job),
+        issues: project_issues_path(project),
+        wiki: project_wiki_path(project, :home),
+        snippets: project_snippets_path(project),
+        merge_requests: project_merge_requests_path(project)
       }
     end
 
     context 'normal user' do
       before do
-        login_as(member)
+        gitlab_sign_in(member)
       end
 
       it 'renders 200 if tool is enabled' do
@@ -130,7 +130,7 @@ describe 'Edit Project Settings', feature: true do
     context 'admin user' do
       before do
         non_member.update_attribute(:admin, true)
-        login_as(non_member)
+        gitlab_sign_in(non_member)
       end
 
       it 'renders 404 if feature is disabled' do
@@ -156,8 +156,8 @@ describe 'Edit Project Settings', feature: true do
   describe 'repository visibility', js: true do
     before do
       project.team << [member, :master]
-      login_as(member)
-      visit edit_namespace_project_path(project.namespace, project)
+      gitlab_sign_in(member)
+      visit edit_project_path(project)
     end
 
     it "disables repository related features" do
@@ -174,7 +174,7 @@ describe 'Edit Project Settings', feature: true do
       click_button "Save changes"
       wait_for_requests
 
-      visit namespace_project_path(project.namespace, project)
+      visit project_path(project)
 
       expect(page).to have_content "Customize your workflow!"
     end
@@ -187,7 +187,7 @@ describe 'Edit Project Settings', feature: true do
       click_button "Save changes"
       wait_for_requests
 
-      visit activity_namespace_project_path(project.namespace, project)
+      visit activity_project_path(project)
 
       page.within(".event-filter") do
         expect(page).to have_selector("a", count: 2)
@@ -205,7 +205,7 @@ describe 'Edit Project Settings', feature: true do
         expect(page).to have_content("Comments")
       end
 
-      visit edit_namespace_project_path(project.namespace, project)
+      visit edit_project_path(project)
 
       select "Disabled", from: "project_project_feature_attributes_merge_requests_access_level"
 
@@ -213,7 +213,7 @@ describe 'Edit Project Settings', feature: true do
         expect(page).to have_content("Comments")
       end
 
-      visit edit_namespace_project_path(project.namespace, project)
+      visit edit_project_path(project)
 
       select "Disabled", from: "project_project_feature_attributes_repository_access_level"
 
@@ -221,14 +221,14 @@ describe 'Edit Project Settings', feature: true do
         expect(page).not_to have_content("Comments")
       end
 
-      visit edit_namespace_project_path(project.namespace, project)
+      visit edit_project_path(project)
     end
 
     def save_changes_and_check_activity_tab
       click_button "Save changes"
       wait_for_requests
 
-      visit activity_namespace_project_path(project.namespace, project)
+      visit activity_project_path(project)
 
       page.within(".event-filter") do
         yield
@@ -242,8 +242,8 @@ describe 'Edit Project Settings', feature: true do
 
     before do
       project.team << [member, :guest]
-      login_as(member)
-      visit namespace_project_path(project.namespace, project)
+      gitlab_sign_in(member)
+      visit project_path(project)
     end
 
     it "does not show project statistic for guest" do
diff --git a/spec/features/projects/files/browse_files_spec.rb b/spec/features/projects/files/browse_files_spec.rb
index 30a1eedbb485259c217fda06b82073ddc4a173f8..ac2b926f4decc59494ef0ce891c5e25db32b51b8 100644
--- a/spec/features/projects/files/browse_files_spec.rb
+++ b/spec/features/projects/files/browse_files_spec.rb
@@ -6,13 +6,13 @@ feature 'user browses project', feature: true, js: true do
 
   before do
     project.team << [user, :master]
-    login_with(user)
-    visit namespace_project_tree_path(project.namespace, project, project.default_branch)
+    gitlab_sign_in(user)
+    visit project_tree_path(project, project.default_branch)
   end
 
   scenario "can see blame of '.gitignore'" do
     click_link ".gitignore"
-    click_link 'Annotate'
+    click_link 'Blame'
 
     expect(page).to have_content "*.rb"
     expect(page).to have_content "Dmitriy Zaporozhets"
diff --git a/spec/features/projects/files/creating_a_file_spec.rb b/spec/features/projects/files/creating_a_file_spec.rb
index 69744ac3948080af7da6699fe570dbccc3674acd..1196994ac3aa99a08acfb0c1be3643c01ace7e4b 100644
--- a/spec/features/projects/files/creating_a_file_spec.rb
+++ b/spec/features/projects/files/creating_a_file_spec.rb
@@ -6,8 +6,8 @@ feature 'User wants to create a file', feature: true do
 
   background do
     project.team << [user, :master]
-    login_as user
-    visit namespace_project_new_blob_path(project.namespace, project, project.default_branch)
+    gitlab_sign_in user
+    visit project_new_blob_path(project, project.default_branch)
   end
 
   def submit_new_file(options)
@@ -30,11 +30,6 @@ feature 'User wants to create a file', feature: true do
     expect(page).to have_content 'The file has been successfully created'
   end
 
-  scenario 'file name contains invalid characters' do
-    submit_new_file(file_name: '\\')
-    expect(page).to have_content 'Path can contain only'
-  end
-
   scenario 'file name contains directory traversal' do
     submit_new_file(file_name: '../README.md')
     expect(page).to have_content 'Path cannot include directory traversal'
diff --git a/spec/features/projects/files/dockerfile_dropdown_spec.rb b/spec/features/projects/files/dockerfile_dropdown_spec.rb
index 93909e91d05372d64168fcc6a14b865177e679f7..783d98dafa7fb7c8e64f1f3a3010dba7acc17460 100644
--- a/spec/features/projects/files/dockerfile_dropdown_spec.rb
+++ b/spec/features/projects/files/dockerfile_dropdown_spec.rb
@@ -7,9 +7,9 @@ feature 'User wants to add a Dockerfile file', feature: true do
     project = create(:project)
     project.team << [user, :master]
 
-    login_as user
+    gitlab_sign_in user
 
-    visit namespace_project_new_blob_path(project.namespace, project, 'master', file_name: 'Dockerfile')
+    visit project_new_blob_path(project, 'master', file_name: 'Dockerfile')
   end
 
   scenario 'user can see Dockerfile dropdown' do
diff --git a/spec/features/projects/files/download_buttons_spec.rb b/spec/features/projects/files/download_buttons_spec.rb
index d7c29a7e07449cc29cbd795f41d52d02ca9f33ac..4f4fab8a6e5e4d57d2947614c165e80d854e0fb9 100644
--- a/spec/features/projects/files/download_buttons_spec.rb
+++ b/spec/features/projects/files/download_buttons_spec.rb
@@ -22,21 +22,18 @@ feature 'Download buttons in files tree', feature: true do
   end
 
   background do
-    login_as(user)
+    gitlab_sign_in(user)
     project.team << [user, role]
   end
 
   describe 'when files tree' do
     context 'with artifacts' do
       before do
-        visit namespace_project_tree_path(
-          project.namespace, project, project.default_branch)
+        visit project_tree_path(project, project.default_branch)
       end
 
       scenario 'shows download artifacts button' do
-        href = latest_succeeded_namespace_project_artifacts_path(
-          project.namespace, project, "#{project.default_branch}/download",
-          job: 'build')
+        href = latest_succeeded_project_artifacts_path(project, "#{project.default_branch}/download", job: 'build')
 
         expect(page).to have_link "Download '#{build.name}'", href: href
       end
diff --git a/spec/features/projects/files/edit_file_soft_wrap_spec.rb b/spec/features/projects/files/edit_file_soft_wrap_spec.rb
index 012befa7990a55cef0c5bff40d3a88a044687ffd..83aea070901607dd3b6c018cf3cb50d5e84a4e3c 100644
--- a/spec/features/projects/files/edit_file_soft_wrap_spec.rb
+++ b/spec/features/projects/files/edit_file_soft_wrap_spec.rb
@@ -5,8 +5,8 @@ feature 'User uses soft wrap whilst editing file', feature: true, js: true do
     user = create(:user)
     project = create(:project)
     project.team << [user, :master]
-    login_as user
-    visit namespace_project_new_blob_path(project.namespace, project, 'master', file_name: 'test_file-name')
+    gitlab_sign_in user
+    visit project_new_blob_path(project, 'master', file_name: 'test_file-name')
     editor = find('.file-editor.code')
     editor.click
     editor.send_keys 'Touch water with paw then recoil in horror chase dog then
diff --git a/spec/features/projects/files/editing_a_file_spec.rb b/spec/features/projects/files/editing_a_file_spec.rb
index 7a3afafec29e7a7734e051a777d60cf4fbc143fe..c9b0dbd0ffb4b73359942b8384717339a137256d 100644
--- a/spec/features/projects/files/editing_a_file_spec.rb
+++ b/spec/features/projects/files/editing_a_file_spec.rb
@@ -17,8 +17,8 @@ feature 'User wants to edit a file', feature: true do
 
   background do
     project.team << [user, :master]
-    login_as user
-    visit namespace_project_edit_blob_path(project.namespace, project,
+    gitlab_sign_in user
+    visit project_edit_blob_path(project,
                                            File.join(project.default_branch, '.gitignore'))
   end
 
diff --git a/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb b/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb
index 5c8105de4cb3f1f31a06914f93e4dc5d9ced565e..07b4aa80f4b055812de9b5ff35bd2b19e37db54e 100644
--- a/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb
+++ b/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb
@@ -6,8 +6,8 @@ feature 'User views files page', feature: true do
 
   before do
     project.team << [user, :master]
-    login_as user
-    visit namespace_project_tree_path(project.namespace, project, project.repository.root_ref)
+    gitlab_sign_in user
+    visit project_tree_path(project, project.repository.root_ref)
   end
 
   scenario 'user sees folders and submodules sorted together, followed by files' do
diff --git a/spec/features/projects/files/find_file_keyboard_spec.rb b/spec/features/projects/files/find_file_keyboard_spec.rb
index ee42bcaec4b6d3245c57480534fe105f40232045..087eef5d4076c81a8c05782a938ab27ac564a464 100644
--- a/spec/features/projects/files/find_file_keyboard_spec.rb
+++ b/spec/features/projects/files/find_file_keyboard_spec.rb
@@ -6,9 +6,9 @@ feature 'Find file keyboard shortcuts', feature: true, js: true do
 
   before do
     project.team << [user, :master]
-    login_as user
+    gitlab_sign_in user
 
-    visit namespace_project_find_file_path(project.namespace, project, project.repository.root_ref)
+    visit project_find_file_path(project, project.repository.root_ref)
 
     wait_for_requests
   end
diff --git a/spec/features/projects/files/find_files_spec.rb b/spec/features/projects/files/find_files_spec.rb
index 716b7591b950a1f413781d8a39134de616bc4b90..d2ccc9a073232bb30b42dcd6ffb817e66ff6e657 100644
--- a/spec/features/projects/files/find_files_spec.rb
+++ b/spec/features/projects/files/find_files_spec.rb
@@ -5,25 +5,18 @@ feature 'Find files button in the tree header', feature: true do
   given(:project) { create(:project) }
 
   background do
-    login_as(user)
+    gitlab_sign_in(user)
     project.team << [user, :developer]
   end
 
   scenario 'project main screen' do
-    visit namespace_project_path(
-      project.namespace,
-      project
-    )
+    visit project_path(project)
 
     expect(page).to have_selector('.tree-controls .shortcuts-find-file')
   end
 
   scenario 'project tree screen' do
-    visit namespace_project_tree_path(
-      project.namespace,
-      project,
-      project.default_branch
-    )
+    visit project_tree_path(project, project.default_branch)
 
     expect(page).to have_selector('.tree-controls .shortcuts-find-file')
   end
diff --git a/spec/features/projects/files/gitignore_dropdown_spec.rb b/spec/features/projects/files/gitignore_dropdown_spec.rb
index e9f49453121771f84008e07e201e9e687cc1e218..23c145d0a1182a1a3268cd17ee92788a594da94f 100644
--- a/spec/features/projects/files/gitignore_dropdown_spec.rb
+++ b/spec/features/projects/files/gitignore_dropdown_spec.rb
@@ -5,8 +5,8 @@ feature 'User wants to add a .gitignore file', feature: true do
     user = create(:user)
     project = create(:project)
     project.team << [user, :master]
-    login_as user
-    visit namespace_project_new_blob_path(project.namespace, project, 'master', file_name: '.gitignore')
+    gitlab_sign_in user
+    visit project_new_blob_path(project, 'master', file_name: '.gitignore')
   end
 
   scenario 'user can see .gitignore dropdown' do
diff --git a/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb
index 031b89d04996fab5a498092518949d0f84d57e80..0539b77e3ddea290bf50377e7c860a828918a229 100644
--- a/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb
+++ b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb
@@ -5,8 +5,8 @@ feature 'User wants to add a .gitlab-ci.yml file', feature: true do
     user = create(:user)
     project = create(:project)
     project.team << [user, :master]
-    login_as user
-    visit namespace_project_new_blob_path(project.namespace, project, 'master', file_name: '.gitlab-ci.yml')
+    gitlab_sign_in user
+    visit project_new_blob_path(project, 'master', file_name: '.gitlab-ci.yml')
   end
 
   scenario 'user can see .gitlab-ci.yml dropdown' do
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 8d410cc3f2e0eb7dc56613a4970050f85b8685b7..3a4ed3d8cf08e8f900751a9e3a2a45107ec0fe3b 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
@@ -7,12 +7,12 @@ feature 'project owner creates a license file', feature: true, js: true do
     project.repository.delete_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)
+    gitlab_sign_in(project_master)
+    visit project_path(project)
   end
 
   scenario 'project master creates a license file manually from a template' do
-    visit namespace_project_tree_path(project.namespace, project, project.repository.root_ref)
+    visit project_tree_path(project, project.repository.root_ref)
     find('.add-to-tree').click
     click_link 'New file'
 
@@ -30,7 +30,7 @@ feature 'project owner creates a license file', feature: true, js: true do
     click_button 'Commit changes'
 
     expect(current_path).to eq(
-      namespace_project_blob_path(project.namespace, project, 'master/LICENSE'))
+      project_blob_path(project, 'master/LICENSE'))
     expect(page).to have_content('MIT License')
     expect(page).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}")
   end
@@ -40,7 +40,7 @@ feature 'project owner creates a license file', feature: true, js: true do
 
     expect(page).to have_content('New file')
     expect(current_path).to eq(
-      namespace_project_new_blob_path(project.namespace, project, 'master'))
+      project_new_blob_path(project, 'master'))
     expect(find('#file_name').value).to eq('LICENSE')
     expect(page).to have_selector('.license-selector')
 
@@ -54,7 +54,7 @@ feature 'project owner creates a license file', feature: true, js: true do
     click_button 'Commit changes'
 
     expect(current_path).to eq(
-      namespace_project_blob_path(project.namespace, project, 'master/LICENSE'))
+      project_blob_path(project, 'master/LICENSE'))
     expect(page).to have_content('MIT License')
     expect(page).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}")
   end
diff --git a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
index 8e197bccabf521c75d8bbd7ffed8eece8ed18fbf..77f97826427006d65a860370c7d85c98168a361a 100644
--- a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
+++ b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
@@ -5,17 +5,17 @@ feature 'project owner sees a link to create a license file in empty project', f
   let(:project) { create(:empty_project) }
   background do
     project.team << [project_master, :master]
-    login_as(project_master)
+    gitlab_sign_in(project_master)
   end
 
   scenario 'project master creates a license file from a template' do
-    visit namespace_project_path(project.namespace, project)
+    visit project_path(project)
     click_link 'Create empty bare repository'
     click_on 'LICENSE'
     expect(page).to have_content('New file')
 
     expect(current_path).to eq(
-      namespace_project_new_blob_path(project.namespace, project, 'master'))
+      project_new_blob_path(project, 'master'))
     expect(find('#file_name').value).to eq('LICENSE')
     expect(page).to have_selector('.license-selector')
 
@@ -31,7 +31,7 @@ feature 'project owner sees a link to create a license file in empty project', f
     click_button 'Commit changes'
 
     expect(current_path).to eq(
-      namespace_project_blob_path(project.namespace, project, 'master/LICENSE'))
+      project_blob_path(project, 'master/LICENSE'))
     expect(page).to have_content('MIT License')
     expect(page).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}")
   end
diff --git a/spec/features/projects/files/template_type_dropdown_spec.rb b/spec/features/projects/files/template_type_dropdown_spec.rb
index 9fcf12e6cb91c983371700c677815ad842008065..53b673538e5e2c46334128c513e25fc5fa98c80b 100644
--- a/spec/features/projects/files/template_type_dropdown_spec.rb
+++ b/spec/features/projects/files/template_type_dropdown_spec.rb
@@ -6,7 +6,7 @@ feature 'Template type dropdown selector', js: true do
 
   before do
     project.team << [user, :master]
-    login_as user
+    gitlab_sign_in user
   end
 
   context 'editing a non-matching file' do
@@ -31,7 +31,7 @@ feature 'Template type dropdown selector', js: true do
 
   context 'editing a matching file' do
     before do
-      visit namespace_project_edit_blob_path(project.namespace, project, File.join(project.default_branch, 'LICENSE'))
+      visit project_edit_blob_path(project, File.join(project.default_branch, 'LICENSE'))
     end
 
     scenario 'displayed' do
@@ -61,7 +61,7 @@ feature 'Template type dropdown selector', js: true do
 
   context 'creating a matching file' do
     before do
-      visit namespace_project_new_blob_path(project.namespace, project, 'master', file_name: '.gitignore')
+      visit project_new_blob_path(project, 'master', file_name: '.gitignore')
     end
 
     scenario 'is displayed' do
@@ -79,7 +79,7 @@ feature 'Template type dropdown selector', js: true do
 
   context 'creating a file' do
     before do
-      visit namespace_project_new_blob_path(project.namespace, project, project.default_branch)
+      visit project_new_blob_path(project, project.default_branch)
     end
 
     scenario 'type selector is shown' do
@@ -129,7 +129,7 @@ def check_type_selector_toggle_text(template_type)
 end
 
 def create_and_edit_file(file_name)
-  visit namespace_project_new_blob_path(project.namespace, project, 'master', file_name: file_name)
+  visit project_new_blob_path(project, 'master', file_name: file_name)
   click_button "Commit changes"
-  visit namespace_project_edit_blob_path(project.namespace, project, File.join(project.default_branch, file_name))
+  visit project_edit_blob_path(project, File.join(project.default_branch, file_name))
 end
diff --git a/spec/features/projects/files/undo_template_spec.rb b/spec/features/projects/files/undo_template_spec.rb
index de10eec05577489f6d68215380319a7347536e02..e18ff42942ff834b12a7825e2e9b6de4bf37dbdc 100644
--- a/spec/features/projects/files/undo_template_spec.rb
+++ b/spec/features/projects/files/undo_template_spec.rb
@@ -6,12 +6,12 @@ feature 'Template Undo Button', js: true do
 
   before do
     project.team << [user, :master]
-    login_as user
+    gitlab_sign_in user
   end
 
   context 'editing a matching file and applying a template' do
     before do
-      visit namespace_project_edit_blob_path(project.namespace, project, File.join(project.default_branch, "LICENSE"))
+      visit project_edit_blob_path(project, File.join(project.default_branch, "LICENSE"))
       select_file_template('.js-license-selector', 'Apache License 2.0')
     end
 
@@ -22,7 +22,7 @@ feature 'Template Undo Button', js: true do
 
   context 'creating a non-matching file' do 
     before do
-      visit namespace_project_new_blob_path(project.namespace, project, 'master')
+      visit project_new_blob_path(project, 'master')
       select_file_template_type('LICENSE')
       select_file_template('.js-license-selector', 'Apache License 2.0')
     end
diff --git a/spec/features/projects/gfm_autocomplete_load_spec.rb b/spec/features/projects/gfm_autocomplete_load_spec.rb
index 67bc9142356fc2b4ccf154dd5772408856483bcd..a8661ad4d249c795eaf9ceebb6fb2b09888ed7ec 100644
--- a/spec/features/projects/gfm_autocomplete_load_spec.rb
+++ b/spec/features/projects/gfm_autocomplete_load_spec.rb
@@ -4,9 +4,9 @@ describe 'GFM autocomplete loading', feature: true, js: true do
   let(:project)   { create(:project) }
 
   before do
-    login_as :admin
+    gitlab_sign_in :admin
 
-    visit namespace_project_path(project.namespace, project)
+    visit project_path(project)
   end
 
   it 'does not load on project#show' do
@@ -14,7 +14,7 @@ describe 'GFM autocomplete loading', feature: true, js: true do
   end
 
   it 'loads on new issue page' do
-    visit new_namespace_project_issue_path(project.namespace, project)
+    visit new_project_issue_path(project)
 
     expect(evaluate_script('gl.GfmAutoComplete.dataSources')).not_to eq({})
   end
diff --git a/spec/features/projects/group_links_spec.rb b/spec/features/projects/group_links_spec.rb
index 1b680a56492a02e446bb2741a7bacb5e2d243244..64415ffe57f3276e72d71b142ecfdef31120f5eb 100644
--- a/spec/features/projects/group_links_spec.rb
+++ b/spec/features/projects/group_links_spec.rb
@@ -9,12 +9,12 @@ feature 'Project group links', :feature, :js do
 
   background do
     project.add_master(master)
-    login_as(master)
+    gitlab_sign_in(master)
   end
 
   context 'setting an expiration date for a group link' do
     before do
-      visit namespace_project_settings_members_path(project.namespace, project)
+      visit project_settings_members_path(project)
 
       click_on 'share-with-group-tab'
 
@@ -43,7 +43,7 @@ feature 'Project group links', :feature, :js do
     end
 
     it 'does not show ancestors', :nested_groups do
-      visit namespace_project_settings_members_path(project.namespace, project)
+      visit project_settings_members_path(project)
 
       click_on 'share-with-group-tab'
       click_link 'Search for a group'
@@ -61,7 +61,7 @@ feature 'Project group links', :feature, :js do
       group.add_owner(master)
       group_two.add_owner(master)
 
-      visit namespace_project_settings_members_path(project.namespace, project)
+      visit project_settings_members_path(project)
       execute_script 'GroupsSelect.PER_PAGE = 1;'
       open_select2 '#link_group_id'
     end
diff --git a/spec/features/projects/guest_navigation_menu_spec.rb b/spec/features/projects/guest_navigation_menu_spec.rb
index b91c3eff478a80579f00e439dfbfeb635238afdf..f6e24a0aa31079ab1b6c6fa2ec05c79c1089d062 100644
--- a/spec/features/projects/guest_navigation_menu_spec.rb
+++ b/spec/features/projects/guest_navigation_menu_spec.rb
@@ -7,11 +7,11 @@ describe 'Guest navigation menu' do
   before do
     project.team << [guest, :guest]
 
-    login_as(guest)
+    gitlab_sign_in(guest)
   end
 
   it 'shows allowed tabs only' do
-    visit namespace_project_path(project.namespace, project)
+    visit project_path(project)
 
     within('.layout-nav') do
       expect(page).to have_content 'Project'
@@ -25,7 +25,7 @@ describe 'Guest navigation menu' do
   end
 
   it 'does not show fork button' do
-    visit namespace_project_path(project.namespace, project)
+    visit project_path(project)
 
     within('.count-buttons') do
       expect(page).not_to have_link 'Fork'
@@ -33,7 +33,7 @@ describe 'Guest navigation menu' do
   end
 
   it 'does not show clone path' do
-    visit namespace_project_path(project.namespace, project)
+    visit project_path(project)
 
     within('.project-repo-buttons') do
       expect(page).not_to have_selector '.project-clone-holder'
@@ -49,7 +49,7 @@ describe 'Guest navigation menu' do
     end
 
     it 'does not show the project file list landing page' do
-      visit namespace_project_path(project.namespace, project)
+      visit project_path(project)
 
       expect(page).not_to have_selector '.project-stats'
       expect(page).not_to have_selector '.project-last-commit'
@@ -58,7 +58,7 @@ describe 'Guest navigation menu' do
     end
 
     it 'shows the customize workflow when issues and wiki are disabled' do
-      visit namespace_project_path(project.namespace, project)
+      visit project_path(project)
 
       expect(page).to have_selector '.project-show-customize_workflow'
     end
@@ -66,7 +66,7 @@ describe 'Guest navigation menu' do
     it 'shows the wiki when enabled' do
       project.project_feature.update!(wiki_access_level: ProjectFeature::PRIVATE)
 
-      visit namespace_project_path(project.namespace, project)
+      visit project_path(project)
 
       expect(page).to have_selector '.project-show-wiki'
     end
@@ -74,7 +74,7 @@ describe 'Guest navigation menu' do
     it 'shows the issues when enabled' do
       project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE)
 
-      visit namespace_project_path(project.namespace, project)
+      visit project_path(project)
 
       expect(page).to have_selector '.issues-list'
     end
diff --git a/spec/features/projects/import_export/export_file_spec.rb b/spec/features/projects/import_export/export_file_spec.rb
index 40caf89dd54c4bdce4b18c3f7ee9fba0455c6867..b7f0ad9197edd60e7830111dac40e6e015365a15 100644
--- a/spec/features/projects/import_export/export_file_spec.rb
+++ b/spec/features/projects/import_export/export_file_spec.rb
@@ -33,17 +33,17 @@ feature 'Import/Export - project export integration test', feature: true, js: tr
 
   context 'admin user' do
     before do
-      login_as(user)
+      gitlab_sign_in(user)
     end
 
     scenario 'exports a project successfully' do
-      visit edit_namespace_project_path(project.namespace, project)
+      visit edit_project_path(project)
 
       expect(page).to have_content('Export project')
 
       click_link 'Export project'
 
-      visit edit_namespace_project_path(project.namespace, project)
+      visit edit_project_path(project)
 
       expect(page).to have_content('Download export')
 
diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb
index 583f479ec1824275c0a2ad6c060a2c2222c1c64f..3f8d225529800f4b7b8619e1c2ddb47d3d5e68af 100644
--- a/spec/features/projects/import_export/import_file_spec.rb
+++ b/spec/features/projects/import_export/import_file_spec.rb
@@ -19,7 +19,7 @@ feature 'Import/Export - project import integration test', feature: true, js: tr
     let!(:namespace) { create(:namespace, name: "asd", owner: user) }
 
     before do
-      login_as(user)
+      gitlab_sign_in(user)
     end
 
     scenario 'user imports an exported project successfully' do
@@ -77,7 +77,7 @@ feature 'Import/Export - project import integration test', feature: true, js: tr
   context 'when limited to the default user namespace' do
     let(:user) { create(:user) }
     before do
-      login_as(user)
+      gitlab_sign_in(user)
     end
 
     scenario 'passes correct namespace ID in the URL' do
@@ -98,6 +98,6 @@ feature 'Import/Export - project import integration test', feature: true, js: tr
   end
 
   def project_hook_exists?(project)
-    Gitlab::Git::Hook.new('post-receive', project.repository.path).exists?
+    Gitlab::Git::Hook.new('post-receive', project).exists?
   end
 end
diff --git a/spec/features/projects/import_export/namespace_export_file_spec.rb b/spec/features/projects/import_export/namespace_export_file_spec.rb
index cb399ea55df4c46b6c770a8f25f210b900046881..f12b28f05fcdeb969f7122c262776d7a10083dce 100644
--- a/spec/features/projects/import_export/namespace_export_file_spec.rb
+++ b/spec/features/projects/import_export/namespace_export_file_spec.rb
@@ -16,7 +16,7 @@ feature 'Import/Export - Namespace export file cleanup', feature: true, js: true
 
   context 'admin user' do
     before do
-      login_as(:admin)
+      gitlab_sign_in(:admin)
     end
 
     context 'moving the namespace' do
@@ -48,13 +48,13 @@ feature 'Import/Export - Namespace export file cleanup', feature: true, js: true
     end
 
     def setup_export_project
-      visit edit_namespace_project_path(project.namespace, project)
+      visit edit_project_path(project)
 
       expect(page).to have_content('Export project')
 
       click_link 'Export project'
 
-      visit edit_namespace_project_path(project.namespace, project)
+      visit edit_project_path(project)
 
       expect(page).to have_content('Download export')
     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 4efd5a26a823ea7a93d6e1c1ce379a05b6171d47..e03e7b881742aab75ca001d26f5c5d4c14c1e040 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 3076c863dcb5455afb47be2df7a98cfa24291a27..83d1dfd91a9897eca1c9ca5fdb6aa56400ce4d56 100644
--- a/spec/features/projects/issuable_templates_spec.rb
+++ b/spec/features/projects/issuable_templates_spec.rb
@@ -6,7 +6,7 @@ feature 'issuable templates', feature: true, js: true do
 
   before do
     project.team << [user, :master]
-    login_as user
+    gitlab_sign_in user
   end
 
   context 'user creates an issue using templates' do
@@ -28,7 +28,7 @@ feature 'issuable templates', feature: true, js: true do
         longtemplate_content,
         message: 'added issue template',
         branch_name: 'master')
-      visit edit_namespace_project_issue_path project.namespace, project, issue
+      visit edit_project_issue_path project, issue
       fill_in :'issue[title]', with: 'test issue title'
     end
 
@@ -81,7 +81,7 @@ feature 'issuable templates', feature: true, js: true do
         template_content,
         message: 'added issue template',
         branch_name: 'master')
-      visit edit_namespace_project_issue_path project.namespace, project, issue
+      visit edit_project_issue_path project, issue
       fill_in :'issue[title]', with: 'test issue title'
       fill_in :'issue[description]', with: prior_description
     end
@@ -105,7 +105,7 @@ feature 'issuable templates', feature: true, js: true do
         template_content,
         message: 'added merge request template',
         branch_name: 'master')
-      visit edit_namespace_project_merge_request_path project.namespace, project, merge_request
+      visit edit_project_merge_request_path project, merge_request
       fill_in :'merge_request[title]', with: 'test merge request title'
     end
 
@@ -124,18 +124,18 @@ feature 'issuable templates', feature: true, js: true do
     let(:merge_request) { create(:merge_request, :with_diffs, source_project: fork_project, target_project: project) }
 
     background do
-      logout
+      gitlab_sign_out
       project.team << [fork_user, :developer]
       fork_project.team << [fork_user, :master]
       create(:forked_project_link, forked_to_project: fork_project, forked_from_project: project)
-      login_as fork_user
+      gitlab_sign_in fork_user
       project.repository.create_file(
         fork_user,
         '.gitlab/merge_request_templates/feature-proposal.md',
         template_content,
         message: 'added merge request template',
         branch_name: 'master')
-      visit edit_namespace_project_merge_request_path project.namespace, project, merge_request
+      visit edit_project_merge_request_path project, merge_request
       fill_in :'merge_request[title]', with: 'test merge request title'
     end
 
diff --git a/spec/features/projects/issues/list_spec.rb b/spec/features/projects/issues/list_spec.rb
index 3137af074cac5e791d83bdaffe10682025a2a54b..380ade24a3264bead40768986ed011548686ccc1 100644
--- a/spec/features/projects/issues/list_spec.rb
+++ b/spec/features/projects/issues/list_spec.rb
@@ -7,13 +7,13 @@ feature 'Issues List' do
   background do
     project.team << [user, :developer]
 
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   scenario 'user does not see create new list button' do
     create(:issue, project: project)
 
-    visit namespace_project_issues_path(project.namespace, project)
+    visit project_issues_path(project)
 
     expect(page).not_to have_selector('.js-new-board-list')
   end
diff --git a/spec/features/projects/issues/rss_spec.rb b/spec/features/projects/issues/rss_spec.rb
index f6852192aef7e5e57e562cbff9591f2320fb37a0..d68606ab54529d0e1dcb962acf4c02480619d38d 100644
--- a/spec/features/projects/issues/rss_spec.rb
+++ b/spec/features/projects/issues/rss_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 feature 'Project Issues RSS' do
   let(:project) { create(:empty_project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
-  let(:path) { namespace_project_issues_path(project.namespace, project) }
+  let(:path) { project_issues_path(project) }
 
   before do
     create(:issue, project: project)
@@ -12,7 +12,7 @@ feature 'Project Issues RSS' do
     before do
       user = create(:user)
       project.team << [user, :developer]
-      login_as(user)
+      gitlab_sign_in(user)
       visit path
     end
 
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index 31c93c75d25ceafa5d7c3fcb2c5c900db3b7eebd..e52151e9585ee70472a39fe72c165a9267b6c670 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -5,7 +5,6 @@ feature 'Jobs', :feature do
   let(:user) { create(:user) }
   let(:user_access_level) { :developer }
   let(:project) { create(:project) }
-  let(:namespace) { project.namespace }
   let(:pipeline) { create(:ci_pipeline, project: project) }
 
   let(:job) { create(:ci_build, :trace, pipeline: pipeline) }
@@ -17,7 +16,7 @@ feature 'Jobs', :feature do
 
   before do
     project.team << [user, user_access_level]
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   describe "GET /:project/jobs" do
@@ -25,7 +24,7 @@ feature 'Jobs', :feature do
 
     context "Pending scope" do
       before do
-        visit namespace_project_jobs_path(project.namespace, project, scope: :pending)
+        visit project_jobs_path(project, scope: :pending)
       end
 
       it "shows Pending tab jobs" do
@@ -40,7 +39,7 @@ feature 'Jobs', :feature do
     context "Running scope" do
       before do
         job.run!
-        visit namespace_project_jobs_path(project.namespace, project, scope: :running)
+        visit project_jobs_path(project, scope: :running)
       end
 
       it "shows Running tab jobs" do
@@ -55,7 +54,7 @@ feature 'Jobs', :feature do
     context "Finished scope" do
       before do
         job.run!
-        visit namespace_project_jobs_path(project.namespace, project, scope: :finished)
+        visit project_jobs_path(project, scope: :finished)
       end
 
       it "shows Finished tab jobs" do
@@ -68,7 +67,7 @@ feature 'Jobs', :feature do
     context "All jobs" do
       before do
         project.builds.running_or_pending.each(&:success)
-        visit namespace_project_jobs_path(project.namespace, project)
+        visit project_jobs_path(project)
       end
 
       it "shows All tab jobs" do
@@ -82,7 +81,7 @@ feature 'Jobs', :feature do
 
     context "when visiting old URL" do
       let(:jobs_url) do
-        namespace_project_jobs_path(project.namespace, project)
+        project_jobs_path(project)
       end
 
       before do
@@ -98,7 +97,7 @@ feature 'Jobs', :feature do
   describe "POST /:project/jobs/:id/cancel_all" do
     before do
       job.run!
-      visit namespace_project_jobs_path(project.namespace, project)
+      visit project_jobs_path(project)
       click_link "Cancel running"
     end
 
@@ -117,7 +116,7 @@ feature 'Jobs', :feature do
       let(:job) { create(:ci_build, :success, pipeline: pipeline) }
 
       before do
-        visit namespace_project_job_path(project.namespace, project, job)
+        visit project_job_path(project, job)
       end
 
       it 'shows status name', :js do
@@ -140,7 +139,7 @@ feature 'Jobs', :feature do
       let(:job) { create(:ci_build, :success, pipeline: pipeline) }
 
       before do
-        visit namespace_project_job_path(project.namespace, project, job)
+        visit project_job_path(project, job)
       end
 
       it 'shows retry button' do
@@ -157,7 +156,7 @@ feature 'Jobs', :feature do
         let(:job) { create(:ci_build, :failed, pipeline: pipeline) }
 
         before do
-          visit namespace_project_job_path(namespace, project, job)
+          visit project_job_path(project, job)
         end
 
         it 'shows New issue button' do
@@ -166,10 +165,10 @@ feature 'Jobs', :feature do
 
         it 'links to issues/new with the title and description filled in' do
           button_title = "Build Failed ##{job.id}"
-          job_path = namespace_project_job_path(namespace, project, job)
+          job_path = project_job_path(project, job)
           options = { issue: { title: button_title, description: job_path } }
 
-          href = new_namespace_project_issue_path(namespace, project, options)
+          href = new_project_issue_path(project, options)
 
           page.within('.header-action-buttons') do
             expect(find('.js-new-issue')['href']).to include(href)
@@ -180,7 +179,7 @@ feature 'Jobs', :feature do
 
     context "Job from other project" do
       before do
-        visit namespace_project_job_path(project.namespace, project, job2)
+        visit project_job_path(project, job2)
       end
 
       it { expect(page.status_code).to eq(404) }
@@ -189,7 +188,7 @@ feature 'Jobs', :feature do
     context "Download artifacts" do
       before do
         job.update_attributes(artifacts_file: artifacts_file)
-        visit namespace_project_job_path(project.namespace, project, job)
+        visit project_job_path(project, job)
       end
 
       it 'has button to download artifacts' do
@@ -202,7 +201,7 @@ feature 'Jobs', :feature do
         job.update_attributes(artifacts_file: artifacts_file,
                               artifacts_expire_at: expire_at)
 
-        visit namespace_project_job_path(project.namespace, project, job)
+        visit project_job_path(project, job)
       end
 
       context 'no expire date defined' do
@@ -248,7 +247,7 @@ feature 'Jobs', :feature do
 
     context "when visiting old URL" do
       let(:job_url) do
-        namespace_project_job_path(project.namespace, project, job)
+        project_job_path(project, job)
       end
 
       before do
@@ -264,7 +263,7 @@ feature 'Jobs', :feature do
       before do
         job.run!
 
-        visit namespace_project_job_path(project.namespace, project, job)
+        visit project_job_path(project, job)
       end
 
       it do
@@ -276,7 +275,7 @@ feature 'Jobs', :feature do
       before do
         job.run!
 
-        visit namespace_project_job_path(project.namespace, project, job)
+        visit project_job_path(project, job)
       end
 
       context 'when job has an initial trace' do
@@ -300,7 +299,7 @@ feature 'Jobs', :feature do
       end
 
       before do
-        visit namespace_project_job_path(project.namespace, project, job)
+        visit project_job_path(project, job)
       end
 
       it 'shows variable key and value after click', js: true do
@@ -325,7 +324,7 @@ feature 'Jobs', :feature do
         let(:job) { create(:ci_build, :success, environment: environment.name, deployments: [deployment], pipeline: pipeline) }
 
         it 'shows a link for the job' do
-          visit namespace_project_job_path(project.namespace, project, job)
+          visit project_job_path(project, job)
 
           expect(page).to have_link environment.name
         end
@@ -335,7 +334,7 @@ feature 'Jobs', :feature do
         let(:job) { create(:ci_build, :failed, environment: environment.name, pipeline: pipeline) }
 
         it 'shows a link for the job' do
-          visit namespace_project_job_path(project.namespace, project, job)
+          visit project_job_path(project, job)
 
           expect(page).to have_link environment.name
         end
@@ -346,7 +345,7 @@ feature 'Jobs', :feature do
         let(:job) { create(:ci_build, :success, environment: environment.name, pipeline: pipeline) }
 
         it 'shows a link to latest deployment' do
-          visit namespace_project_job_path(project.namespace, project, job)
+          visit project_job_path(project, job)
 
           expect(page).to have_link('latest deployment')
         end
@@ -358,7 +357,7 @@ feature 'Jobs', :feature do
     context "Job from project" do
       before do
         job.run!
-        visit namespace_project_job_path(project.namespace, project, job)
+        visit project_job_path(project, job)
         find('.js-cancel-job').click()
       end
 
@@ -373,7 +372,7 @@ feature 'Jobs', :feature do
     context "Job from project", :js do
       before do
         job.run!
-        visit namespace_project_job_path(project.namespace, project, job)
+        visit project_job_path(project, job)
         find('.js-cancel-job').click()
         find('.js-retry-button').trigger('click')
       end
@@ -392,9 +391,9 @@ feature 'Jobs', :feature do
         job.cancel!
         project.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
 
-        logout_direct
-        login_with(create(:user))
-        visit namespace_project_job_path(project.namespace, project, job)
+        gitlab_sign_out_direct
+        gitlab_sign_in(create(:user))
+        visit project_job_path(project, job)
       end
 
       it 'does not show the Retry button' do
@@ -408,14 +407,14 @@ feature 'Jobs', :feature do
   describe "GET /:project/jobs/:id/download" do
     before do
       job.update_attributes(artifacts_file: artifacts_file)
-      visit namespace_project_job_path(project.namespace, project, job)
+      visit project_job_path(project, job)
       click_link 'Download'
     end
 
     context "Build from other project" do
       before do
         job2.update_attributes(artifacts_file: artifacts_file)
-        visit download_namespace_project_job_artifacts_path(project.namespace, project, job2)
+        visit download_project_job_artifacts_path(project, job2)
       end
 
       it { expect(page.status_code).to eq(404) }
@@ -428,7 +427,7 @@ feature 'Jobs', :feature do
         before do
           Capybara.current_session.driver.headers = { 'X-Sendfile-Type' => 'X-Sendfile' }
           job.run!
-          visit namespace_project_job_path(project.namespace, project, job)
+          visit project_job_path(project, job)
           find('.js-raw-link-controller').click()
         end
 
@@ -443,7 +442,7 @@ feature 'Jobs', :feature do
         before do
           Capybara.current_session.driver.headers = { 'X-Sendfile-Type' => 'X-Sendfile' }
           job2.run!
-          visit raw_namespace_project_job_path(project.namespace, project, job2)
+          visit raw_project_job_path(project, job2)
         end
 
         it 'sends the right headers' do
@@ -467,7 +466,7 @@ feature 'Jobs', :feature do
             .to receive(:paths)
             .and_return([existing_file])
 
-          visit namespace_project_job_path(namespace, project, job)
+          visit project_job_path(project, job)
 
           find('.js-raw-link-controller').click
         end
@@ -485,7 +484,7 @@ feature 'Jobs', :feature do
             .to receive(:paths)
             .and_return([])
 
-          visit namespace_project_job_path(namespace, project, job)
+          visit project_job_path(project, job)
         end
 
         it 'sends the right headers' do
@@ -496,7 +495,7 @@ feature 'Jobs', :feature do
 
     context "when visiting old URL" do
       let(:raw_job_url) do
-        raw_namespace_project_job_path(project.namespace, project, job)
+        raw_project_job_path(project, job)
       end
 
       before do
@@ -512,7 +511,7 @@ feature 'Jobs', :feature do
   describe "GET /:project/jobs/:id/trace.json" do
     context "Job from project" do
       before do
-        visit trace_namespace_project_job_path(project.namespace, project, job, format: :json)
+        visit trace_project_job_path(project, job, format: :json)
       end
 
       it { expect(page.status_code).to eq(200) }
@@ -520,7 +519,7 @@ feature 'Jobs', :feature do
 
     context "Job from other project" do
       before do
-        visit trace_namespace_project_job_path(project.namespace, project, job2, format: :json)
+        visit trace_project_job_path(project, job2, format: :json)
       end
 
       it { expect(page.status_code).to eq(404) }
@@ -530,7 +529,7 @@ feature 'Jobs', :feature do
   describe "GET /:project/jobs/:id/status" do
     context "Job from project" do
       before do
-        visit status_namespace_project_job_path(project.namespace, project, job)
+        visit status_project_job_path(project, job)
       end
 
       it { expect(page.status_code).to eq(200) }
@@ -538,7 +537,7 @@ feature 'Jobs', :feature do
 
     context "Job from other project" do
       before do
-        visit status_namespace_project_job_path(project.namespace, project, job2)
+        visit status_project_job_path(project, job2)
       end
 
       it { expect(page.status_code).to eq(404) }
diff --git a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
index e2911a37e408c46ca8e02bcb69d78985e4f6baf0..89e31a72869fdea140046942a5c823faed25cf40 100644
--- a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
+++ b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
@@ -28,8 +28,8 @@ feature 'Issue prioritization', feature: true do
       issue_2.labels << label_4
       issue_1.labels << label_5
 
-      login_as user
-      visit namespace_project_issues_path(project.namespace, project, sort: 'label_priority')
+      gitlab_sign_in user
+      visit project_issues_path(project, sort: 'label_priority')
 
       # Ensure we are indicating that issues are sorted by priority
       expect(page).to have_selector('.dropdown-toggle', text: 'Label priority')
@@ -67,8 +67,8 @@ feature 'Issue prioritization', feature: true do
       issue_4.labels << label_4 # 7
       issue_6.labels << label_5 # 8 - No priority
 
-      login_as user
-      visit namespace_project_issues_path(project.namespace, project, sort: 'label_priority')
+      gitlab_sign_in user
+      visit project_issues_path(project, sort: 'label_priority')
 
       expect(page).to have_selector('.dropdown-toggle', text: 'Label priority')
 
diff --git a/spec/features/projects/labels/subscription_spec.rb b/spec/features/projects/labels/subscription_spec.rb
index 3130d87fba5abbd44fe642f54713c7ccd160961e..04617bfe03e2736b6eb6e9a6ae4e41b6099ad9b5 100644
--- a/spec/features/projects/labels/subscription_spec.rb
+++ b/spec/features/projects/labels/subscription_spec.rb
@@ -10,11 +10,11 @@ feature 'Labels subscription', feature: true do
   context 'when signed in' do
     before do
       project.team << [user, :developer]
-      login_as user
+      gitlab_sign_in user
     end
 
     scenario 'users can subscribe/unsubscribe to labels', js: true do
-      visit namespace_project_labels_path(project.namespace, project)
+      visit project_labels_path(project)
 
       expect(page).to have_content('bug')
       expect(page).to have_content('feature')
@@ -55,7 +55,7 @@ feature 'Labels subscription', feature: true do
 
   context 'when not signed in' do
     it 'users can not subscribe/unsubscribe to labels' do
-      visit namespace_project_labels_path(project.namespace, project)
+      visit project_labels_path(project)
 
       expect(page).to have_content 'bug'
       expect(page).to have_content 'feature'
diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb
index 34fafe072a39ea72abf0d4968f7e3a156ff546a8..034613ea6beeddc7833d8a691349e8b6f60b9e12 100644
--- a/spec/features/projects/labels/update_prioritization_spec.rb
+++ b/spec/features/projects/labels/update_prioritization_spec.rb
@@ -14,11 +14,11 @@ feature 'Prioritize labels', feature: true do
     before do
       project.team << [user, :developer]
 
-      login_as user
+      gitlab_sign_in user
     end
 
     scenario 'user can prioritize a group label', js: true do
-      visit namespace_project_labels_path(project.namespace, project)
+      visit project_labels_path(project)
 
       expect(page).to have_content('Star labels to start sorting by priority')
 
@@ -37,7 +37,7 @@ feature 'Prioritize labels', feature: true do
     scenario 'user can unprioritize a group label', js: true do
       create(:label_priority, project: project, label: feature, priority: 1)
 
-      visit namespace_project_labels_path(project.namespace, project)
+      visit project_labels_path(project)
 
       page.within('.prioritized-labels') do
         expect(page).to have_content('feature')
@@ -53,7 +53,7 @@ feature 'Prioritize labels', feature: true do
     end
 
     scenario 'user can prioritize a project label', js: true do
-      visit namespace_project_labels_path(project.namespace, project)
+      visit project_labels_path(project)
 
       expect(page).to have_content('Star labels to start sorting by priority')
 
@@ -72,7 +72,7 @@ feature 'Prioritize labels', feature: true do
     scenario 'user can unprioritize a project label', js: true do
       create(:label_priority, project: project, label: bug, priority: 1)
 
-      visit namespace_project_labels_path(project.namespace, project)
+      visit project_labels_path(project)
 
       page.within('.prioritized-labels') do
         expect(page).to have_content('bug')
@@ -92,7 +92,7 @@ feature 'Prioritize labels', feature: true do
       create(:label_priority, project: project, label: bug, priority: 1)
       create(:label_priority, project: project, label: feature, priority: 2)
 
-      visit namespace_project_labels_path(project.namespace, project)
+      visit project_labels_path(project)
 
       expect(page).to have_content 'bug'
       expect(page).to have_content 'feature'
@@ -120,9 +120,9 @@ feature 'Prioritize labels', feature: true do
     it 'does not prioritize labels' do
       guest = create(:user)
 
-      login_as guest
+      gitlab_sign_in guest
 
-      visit namespace_project_labels_path(project.namespace, project)
+      visit project_labels_path(project)
 
       expect(page).to have_content 'bug'
       expect(page).to have_content 'wontfix'
@@ -133,7 +133,7 @@ feature 'Prioritize labels', feature: true do
 
   context 'as a non signed in user' do
     it 'does not prioritize labels' do
-      visit namespace_project_labels_path(project.namespace, project)
+      visit project_labels_path(project)
 
       expect(page).to have_content 'bug'
       expect(page).to have_content 'wontfix'
diff --git a/spec/features/projects/main/download_buttons_spec.rb b/spec/features/projects/main/download_buttons_spec.rb
index 02198ff3e419f0058c796886d3c6dbabb2a65067..b14e0f089f04c95efa5d4a7b392db3acecb35be0 100644
--- a/spec/features/projects/main/download_buttons_spec.rb
+++ b/spec/features/projects/main/download_buttons_spec.rb
@@ -22,20 +22,18 @@ feature 'Download buttons in project main page', feature: true do
   end
 
   background do
-    login_as(user)
+    gitlab_sign_in(user)
     project.team << [user, role]
   end
 
   describe 'when checking project main page' do
     context 'with artifacts' do
       before do
-        visit namespace_project_path(project.namespace, project)
+        visit project_path(project)
       end
 
       scenario 'shows download artifacts button' do
-        href = latest_succeeded_namespace_project_artifacts_path(
-          project.namespace, project, "#{project.default_branch}/download",
-          job: 'build')
+        href = latest_succeeded_project_artifacts_path(project, "#{project.default_branch}/download", job: 'build')
 
         expect(page).to have_link "Download '#{build.name}'", href: href
       end
diff --git a/spec/features/projects/main/rss_spec.rb b/spec/features/projects/main/rss_spec.rb
index 53966229a2a28f0a81f22b75fe05788ff81ac328..5f48253dd06c70e39a5d92b01178987a5e8f75c2 100644
--- a/spec/features/projects/main/rss_spec.rb
+++ b/spec/features/projects/main/rss_spec.rb
@@ -2,13 +2,13 @@ require 'spec_helper'
 
 feature 'Project RSS' do
   let(:project) { create(:project, :repository, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
-  let(:path) { namespace_project_path(project.namespace, project) }
+  let(:path) { project_path(project) }
 
   context 'when signed in' do
     before do
       user = create(:user)
       project.team << [user, :developer]
-      login_as(user)
+      gitlab_sign_in(user)
       visit path
     end
 
diff --git a/spec/features/projects/members/anonymous_user_sees_members_spec.rb b/spec/features/projects/members/anonymous_user_sees_members_spec.rb
index d82cf53c690fac4d50506eb0a69f717490b4d5ff..4958d5594acbfef5d6cf0ab17e4d93c0cbc0ef8b 100644
--- a/spec/features/projects/members/anonymous_user_sees_members_spec.rb
+++ b/spec/features/projects/members/anonymous_user_sees_members_spec.rb
@@ -11,10 +11,10 @@ feature 'Projects > Members > Anonymous user sees members', feature: true do
   end
 
   scenario "anonymous user visits the project's members page and sees the list of members" do
-    visit namespace_project_settings_members_path(project.namespace, project)
+    visit project_settings_members_path(project)
 
     expect(current_path).to eq(
-      namespace_project_settings_members_path(project.namespace, project))
+      project_settings_members_path(project))
     expect(page).to have_content(user.name)
   end
 end
diff --git a/spec/features/projects/members/group_links_spec.rb b/spec/features/projects/members/group_links_spec.rb
index 3d253f014840765d6c0c1449849be415365601af..61cd7db15f5ae2a576be1408ab22f0aee473fcae 100644
--- a/spec/features/projects/members/group_links_spec.rb
+++ b/spec/features/projects/members/group_links_spec.rb
@@ -9,8 +9,8 @@ feature 'Projects > Members > Anonymous user sees members', feature: true, js: t
     project.team << [user, :master]
     @group_link = create(:project_group_link, project: project, group: group)
 
-    login_as(user)
-    visit namespace_project_settings_members_path(project.namespace, project)
+    gitlab_sign_in(user)
+    visit project_settings_members_path(project)
   end
 
   it 'updates group access level' do
@@ -22,7 +22,7 @@ feature 'Projects > Members > Anonymous user sees members', feature: true, js: t
 
     wait_for_requests
 
-    visit namespace_project_settings_members_path(project.namespace, project)
+    visit project_settings_members_path(project)
 
     expect(first('.group_member')).to have_content('Guest')
   end
diff --git a/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb b/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb
index b483ba4c54c6909ba58e5868a99367ceea8e0863..1c429202abad6d1421ab3b6e4c2973636846750c 100644
--- a/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb
+++ b/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb
@@ -7,8 +7,8 @@ feature 'Projects > Members > Group member cannot leave group project', feature:
 
   background do
     group.add_developer(user)
-    login_as(user)
-    visit namespace_project_path(project.namespace, project)
+    gitlab_sign_in(user)
+    visit project_path(project)
   end
 
   scenario 'user does not see a "Leave project" link' do
diff --git a/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb b/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb
index ff9b60078066a98d6bb4e23469d20e40c964fd91..7250a0d26fc9bf73a295501d9f3a351004d7974d 100644
--- a/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb
+++ b/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb
@@ -41,7 +41,7 @@ feature 'Projects > Members > Group member cannot request access to his group pr
   end
 
   def login_and_visit_project_page(user)
-    login_as(user)
-    visit namespace_project_path(project.namespace, project)
+    gitlab_sign_in(user)
+    visit project_path(project)
   end
 end
diff --git a/spec/features/projects/members/group_members_spec.rb b/spec/features/projects/members/group_members_spec.rb
index 3385e5972ff7438442263ce56e08cc403ca4bf0e..0acf5134cce5831dad94dab4f8dbe3760b2e5bb3 100644
--- a/spec/features/projects/members/group_members_spec.rb
+++ b/spec/features/projects/members/group_members_spec.rb
@@ -13,13 +13,13 @@ feature 'Projects members', feature: true do
   background do
     project.team << [developer, :developer]
     group.add_owner(user)
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   context 'with a group invitee' do
     before do
       group_invitee
-      visit namespace_project_settings_members_path(project.namespace, project)
+      visit project_settings_members_path(project)
     end
 
     scenario 'does not appear in the project members page' do
@@ -33,7 +33,7 @@ feature 'Projects members', feature: true do
     before do
       group_invitee
       project_invitee
-      visit namespace_project_settings_members_path(project.namespace, project)
+      visit project_settings_members_path(project)
     end
 
     scenario 'shows the project invitee, the project developer, and the group owner' do
@@ -54,7 +54,7 @@ feature 'Projects members', feature: true do
   context 'with a group requester' do
     before do
       group.request_access(group_requester)
-      visit namespace_project_settings_members_path(project.namespace, project)
+      visit project_settings_members_path(project)
     end
 
     scenario 'does not appear in the project members page' do
@@ -68,7 +68,7 @@ feature 'Projects members', feature: true do
     before do
       group.request_access(group_requester)
       project.request_access(project_requester)
-      visit namespace_project_settings_members_path(project.namespace, project)
+      visit project_settings_members_path(project)
     end
 
     scenario 'shows the project requester, the project developer, and the group owner' do
diff --git a/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb b/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb
index bdeeef572730d272eef97a549b77dda4fd159461..5a28a7538f8fcc52e0ee56b5e9479d786ed7268b 100644
--- a/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb
+++ b/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb
@@ -8,10 +8,10 @@ feature 'Projects > Members > Group requester cannot request access to project',
 
   background do
     group.add_owner(owner)
-    login_as(user)
+    gitlab_sign_in(user)
     visit group_path(group)
     perform_enqueued_jobs { click_link 'Request Access' }
-    visit namespace_project_path(project.namespace, project)
+    visit project_path(project)
   end
 
   scenario 'group requester does not see the request access / withdraw access request button' do
diff --git a/spec/features/projects/members/list_spec.rb b/spec/features/projects/members/list_spec.rb
index deea34214fb46f7d325e0264d908c70b53a33bde..b62bf2f62930c7845617bd333db74de378efd29b 100644
--- a/spec/features/projects/members/list_spec.rb
+++ b/spec/features/projects/members/list_spec.rb
@@ -9,7 +9,7 @@ feature 'Project members list', feature: true do
   let(:project) { create(:project, namespace: group) }
 
   background do
-    login_as(user1)
+    gitlab_sign_in(user1)
     group.add_owner(user1)
   end
 
@@ -85,6 +85,6 @@ feature 'Project members list', feature: true do
   end
 
   def visit_members_page
-    visit namespace_project_settings_members_path(project.namespace, project)
+    visit project_settings_members_path(project)
   end
 end
diff --git a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
index 1e6f15d8258b394ee3ea5666df8e14807c1e174f..ca2172bb905903a84019d2db0a162f69d6e0fb77 100644
--- a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
+++ b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
@@ -10,13 +10,13 @@ feature 'Projects > Members > Master adds member with expiration date', feature:
 
   background do
     project.team << [master, :master]
-    login_as(master)
+    gitlab_sign_in(master)
   end
 
   scenario 'expiration date is displayed in the members list' do
     travel_to Time.zone.parse('2016-08-06 08:00') do
       date = 4.days.from_now
-      visit namespace_project_project_members_path(project.namespace, project)
+      visit project_project_members_path(project)
 
       page.within '.users-project-form' do
         select2(new_member.id, from: '#user_ids', multiple: true)
@@ -34,7 +34,7 @@ feature 'Projects > Members > Master adds member with expiration date', feature:
     travel_to Time.zone.parse('2016-08-06 08:00') do
       date = 3.days.from_now
       project.team.add_users([new_member.id], :developer, expires_at: Date.today.to_s(:medium))
-      visit namespace_project_project_members_path(project.namespace, project)
+      visit project_project_members_path(project)
 
       page.within "#project_member_#{new_member.project_members.first.id}" do
         find('.js-access-expiration-date').set date.to_s(:medium)
diff --git a/spec/features/projects/members/master_manages_access_requests_spec.rb b/spec/features/projects/members/master_manages_access_requests_spec.rb
index 143390b71cdb8fd4a1f85535d26b41407ad4e075..69c5927428c251c1a1866dd406a111e72eacddbd 100644
--- a/spec/features/projects/members/master_manages_access_requests_spec.rb
+++ b/spec/features/projects/members/master_manages_access_requests_spec.rb
@@ -8,17 +8,17 @@ feature 'Projects > Members > Master manages access requests', feature: true do
   background do
     project.request_access(user)
     project.team << [master, :master]
-    login_as(master)
+    gitlab_sign_in(master)
   end
 
   scenario 'master can see access requests' do
-    visit namespace_project_project_members_path(project.namespace, project)
+    visit project_project_members_path(project)
 
     expect_visible_access_request(project, user)
   end
 
   scenario 'master can grant access' do
-    visit namespace_project_project_members_path(project.namespace, project)
+    visit project_project_members_path(project)
 
     expect_visible_access_request(project, user)
 
@@ -29,7 +29,7 @@ feature 'Projects > Members > Master manages access requests', feature: true do
   end
 
   scenario 'master can deny access' do
-    visit namespace_project_project_members_path(project.namespace, project)
+    visit project_project_members_path(project)
 
     expect_visible_access_request(project, user)
 
diff --git a/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb b/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb
index 9564347e7335c1785559b9cd8a371a6e037728e4..f0da201da85d95c2cab8020a2122e8981737590c 100644
--- a/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb
+++ b/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb
@@ -6,8 +6,8 @@ feature 'Projects > Members > Member cannot request access to his project', feat
 
   background do
     project.team << [member, :developer]
-    login_as(member)
-    visit namespace_project_path(project.namespace, project)
+    gitlab_sign_in(member)
+    visit project_path(project)
   end
 
   scenario 'member does not see the request access button' do
diff --git a/spec/features/projects/members/member_leaves_project_spec.rb b/spec/features/projects/members/member_leaves_project_spec.rb
index 5daa932e4e6e52481f2f01f45eca77d74c823ba3..31d8bbdc0b6021c4aac64d862e1b886987fae260 100644
--- a/spec/features/projects/members/member_leaves_project_spec.rb
+++ b/spec/features/projects/members/member_leaves_project_spec.rb
@@ -6,8 +6,8 @@ feature 'Projects > Members > Member leaves project', feature: true do
 
   background do
     project.team << [user, :developer]
-    login_as(user)
-    visit namespace_project_path(project.namespace, project)
+    gitlab_sign_in(user)
+    visit project_path(project)
   end
 
   scenario 'user leaves project' do
diff --git a/spec/features/projects/members/owner_cannot_leave_project_spec.rb b/spec/features/projects/members/owner_cannot_leave_project_spec.rb
index b26d55c5d5d186aca1f7a0c2c2ae4a0fea6af00f..a1ccc6ddf65425ca25e68c4bcbdb56087b9af1fb 100644
--- a/spec/features/projects/members/owner_cannot_leave_project_spec.rb
+++ b/spec/features/projects/members/owner_cannot_leave_project_spec.rb
@@ -4,8 +4,8 @@ feature 'Projects > Members > Owner cannot leave project', feature: true do
   let(:project) { create(:project) }
 
   background do
-    login_as(project.owner)
-    visit namespace_project_path(project.namespace, project)
+    gitlab_sign_in(project.owner)
+    visit project_path(project)
   end
 
   scenario 'user does not see a "Leave project" link' do
diff --git a/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb b/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb
index 4ca9272b9c10736f4584ea23c280b79bc898a26a..54f5d0d165ba75f0de08dfb0a011774a7518c72c 100644
--- a/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb
+++ b/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb
@@ -4,8 +4,8 @@ feature 'Projects > Members > Owner cannot request access to his project', featu
   let(:project) { create(:project) }
 
   background do
-    login_as(project.owner)
-    visit namespace_project_path(project.namespace, project)
+    gitlab_sign_in(project.owner)
+    visit project_path(project)
   end
 
   scenario 'owner does not see the request access button' do
diff --git a/spec/features/projects/members/sorting_spec.rb b/spec/features/projects/members/sorting_spec.rb
index d428f6fcf2237941810e8228d1b7f229ad5cd95f..7c02b49a0ab92ed523e3cab15319e57f874f4aac 100644
--- a/spec/features/projects/members/sorting_spec.rb
+++ b/spec/features/projects/members/sorting_spec.rb
@@ -8,7 +8,7 @@ feature 'Projects > Members > Sorting', feature: true do
   background do
     create(:project_member, :developer, user: developer, project: project, created_at: 3.days.ago)
 
-    login_as(master)
+    gitlab_sign_in(master)
   end
 
   scenario 'sorts alphabetically by default' do
@@ -84,7 +84,7 @@ feature 'Projects > Members > Sorting', feature: true do
   end
 
   def visit_members_list(sort:)
-    visit namespace_project_project_members_path(project.namespace.to_param, project, sort: sort)
+    visit project_project_members_path(project, sort: sort)
   end
 
   def first_member
diff --git a/spec/features/projects/members/user_requests_access_spec.rb b/spec/features/projects/members/user_requests_access_spec.rb
index ec48a4bd726ab29114780ff4285419ecd767a876..247cc0e6f2c9ec5e41ad02c13a46e695ea1c5bd4 100644
--- a/spec/features/projects/members/user_requests_access_spec.rb
+++ b/spec/features/projects/members/user_requests_access_spec.rb
@@ -6,13 +6,13 @@ feature 'Projects > Members > User requests access', feature: true do
   let(:master) { project.owner }
 
   background do
-    login_as(user)
-    visit namespace_project_path(project.namespace, project)
+    gitlab_sign_in(user)
+    visit project_path(project)
   end
 
   scenario 'request access feature is disabled' do
     project.update_attributes(request_access_enabled: false)
-    visit namespace_project_path(project.namespace, project)
+    visit project_path(project)
 
     expect(page).not_to have_content 'Request Access'
   end
@@ -35,7 +35,7 @@ feature 'Projects > Members > User requests access', feature: true do
       project.project_feature.update!(repository_access_level: ProjectFeature::PRIVATE,
                                       builds_access_level: ProjectFeature::PRIVATE,
                                       merge_requests_access_level: ProjectFeature::PRIVATE)
-      visit namespace_project_path(project.namespace, project)
+      visit project_path(project)
 
       expect(page).to have_content 'Request Access'
     end
@@ -49,7 +49,7 @@ feature 'Projects > Members > User requests access', feature: true do
     open_project_settings_menu
     click_link 'Members'
 
-    visit namespace_project_settings_members_path(project.namespace, project)
+    visit project_settings_members_path(project)
     page.within('.content') do
       expect(page).not_to have_content(user.name)
     end
diff --git a/spec/features/projects/merge_request_button_spec.rb b/spec/features/projects/merge_request_button_spec.rb
index 1370ab1c5217b4b2121987ef1ec840e9c96bc72b..771dd7d3208a5cc41fb3096da24853f2fd1c0523 100644
--- a/spec/features/projects/merge_request_button_spec.rb
+++ b/spec/features/projects/merge_request_button_spec.rb
@@ -18,15 +18,14 @@ feature 'Merge Request button', feature: true do
 
     context 'logged in as developer' do
       before do
-        login_as(user)
+        gitlab_sign_in(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' })
+        href = project_new_merge_request_path(project,
+                                              merge_request: { source_branch: 'feature',
+                                                               target_branch: 'master' })
 
         visit url
 
@@ -52,7 +51,7 @@ feature 'Merge Request button', feature: true do
 
     context 'logged in as non-member' do
       before do
-        login_as(user)
+        gitlab_sign_in(user)
       end
 
       it 'does not show Create merge request button' do
@@ -67,10 +66,9 @@ feature 'Merge Request button', feature: true 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' })
+          href = project_new_merge_request_path(forked_project,
+                                                merge_request: { source_branch: 'feature',
+                                                                 target_branch: 'master' })
 
           visit fork_url
 
@@ -85,24 +83,24 @@ feature 'Merge Request button', feature: true do
   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, search: 'feature') }
-      let(:fork_url) { namespace_project_branches_path(forked_project.namespace, forked_project, search: 'feature') }
+      let(:url) { project_branches_path(project, search: 'feature') }
+      let(:fork_url) { project_branches_path(forked_project, search: 'feature') }
     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') }
+      let(:url) { project_compare_path(project, from: 'master', to: 'feature') }
+      let(:fork_url) { project_compare_path(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') }
+      let(:url) { project_commits_path(project, 'feature') }
+      let(:fork_url) { project_commits_path(forked_project, 'feature') }
     end
   end
 end
diff --git a/spec/features/projects/merge_requests/list_spec.rb b/spec/features/projects/merge_requests/list_spec.rb
index 7e8a796c55d3c6c7cce01ec61471daf1de997d0b..ff4d22b3881ad9c4af37a8a54b023beaa23028ff 100644
--- a/spec/features/projects/merge_requests/list_spec.rb
+++ b/spec/features/projects/merge_requests/list_spec.rb
@@ -7,34 +7,34 @@ feature 'Merge Requests List' do
   background do
     project.team << [user, :developer]
 
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   scenario 'user does not see create new list button' do
     create(:merge_request, source_project: project)
 
-    visit namespace_project_merge_requests_path(project.namespace, project)
+    visit project_merge_requests_path(project)
 
     expect(page).not_to have_selector('.js-new-board-list')
   end
 
   it 'should show an empty state' do
-    visit namespace_project_merge_requests_path(project.namespace, project)
+    visit project_merge_requests_path(project)
 
     expect(page).to have_selector('.empty-state')
   end
 
   it 'empty state should have a create merge request button' do
-    visit namespace_project_merge_requests_path(project.namespace, project)
+    visit project_merge_requests_path(project)
 
-    expect(page).to have_link 'New merge request', href: new_namespace_project_merge_request_path(project.namespace, project)
+    expect(page).to have_link 'New merge request', href: project_new_merge_request_path(project)
   end
 
   context 'if there are merge requests' do
     before do
       create(:merge_request, assignee: user, source_project: project)
 
-      visit namespace_project_merge_requests_path(project.namespace, project)
+      visit project_merge_requests_path(project)
     end
 
     it 'should not show an empty state' do
diff --git a/spec/features/projects/milestones/milestone_spec.rb b/spec/features/projects/milestones/milestone_spec.rb
index b4fc0edbde822b81001546eb0105d71bf5947d33..1913ef728d31c73ff215e0309ec80249039485c3 100644
--- a/spec/features/projects/milestones/milestone_spec.rb
+++ b/spec/features/projects/milestones/milestone_spec.rb
@@ -6,12 +6,12 @@ feature 'Project milestone', :feature do
   let(:milestone) { create(:milestone, project: project) }
 
   before do
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   context 'when project has enabled issues' do
     before do
-      visit namespace_project_milestone_path(project.namespace, project, milestone)
+      visit project_milestone_path(project, milestone)
     end
 
     it 'shows issues tab' do
@@ -38,7 +38,7 @@ feature 'Project milestone', :feature do
   context 'when project has disabled issues' do
     before do
       project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
-      visit namespace_project_milestone_path(project.namespace, project, milestone)
+      visit project_milestone_path(project, milestone)
     end
 
     it 'hides issues tab' do
@@ -68,7 +68,7 @@ feature 'Project milestone', :feature do
     before do
       create(:issue, project: project, milestone: milestone)
 
-      visit namespace_project_milestone_path(project.namespace, project, milestone)
+      visit project_milestone_path(project, milestone)
     end
 
     describe 'the collapsed sidebar' do
diff --git a/spec/features/projects/milestones/milestones_sorting_spec.rb b/spec/features/projects/milestones/milestones_sorting_spec.rb
index da3eaed707a85b83d9c5d130835698c477728d7b..1b74758445ba78463e33add18f5f313ec638dfe0 100644
--- a/spec/features/projects/milestones/milestones_sorting_spec.rb
+++ b/spec/features/projects/milestones/milestones_sorting_spec.rb
@@ -15,11 +15,11 @@ feature 'Milestones sorting', :feature, :js do
       due_date: 11.days.from_now,
       created_at:  1.hour.ago,
       title: "bbb", project: project)
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   scenario 'visit project milestones and sort by due_date_asc' do
-    visit namespace_project_milestones_path(project.namespace, project)
+    visit project_milestones_path(project)
 
     expect(page).to have_button('Due soon')
 
diff --git a/spec/features/projects/milestones/new_spec.rb b/spec/features/projects/milestones/new_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3c81db502bc9f7f7ad9d9593c07c4eaa6c535780
--- /dev/null
+++ b/spec/features/projects/milestones/new_spec.rb
@@ -0,0 +1,18 @@
+require 'spec_helper'
+
+feature 'Creating a new project milestone', :feature, :js do
+  let(:user) { create(:user) }
+  let(:project) { create(:empty_project, name: 'test', namespace: user.namespace) }
+
+  before do
+    login_as(user)
+    visit new_project_milestone_path(project)
+  end
+
+  it 'description has autocomplete' do
+    find('#milestone_description').native.send_keys('')
+    fill_in 'milestone_description', with: '@'
+
+    expect(page).to have_selector('.atwho-view')
+  end
+end
diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb
index b1f9eb1566787b002a10a23729e22a0389316474..22fb122373983a00f60fb9049c2b6d4274e9c20d 100644
--- a/spec/features/projects/new_project_spec.rb
+++ b/spec/features/projects/new_project_spec.rb
@@ -1,13 +1,27 @@
-require "spec_helper"
+require 'spec_helper'
 
-feature "New project", feature: true do
+feature 'New project' do
   let(:user) { create(:admin) }
 
   before do
-    login_as(user)
+    sign_in(user)
   end
 
-  context "Visibility level selector" do
+  it 'shows "New project" page' do
+    visit new_project_path
+
+    expect(page).to have_content('Project path')
+    expect(page).to have_content('Project name')
+
+    expect(page).to have_link('GitHub')
+    expect(page).to have_link('Bitbucket')
+    expect(page).to have_link('GitLab.com')
+    expect(page).to have_link('Google Code')
+    expect(page).to have_button('Repo by URL')
+    expect(page).to have_link('GitLab export')
+  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)
@@ -28,20 +42,20 @@ feature "New project", feature: true do
     end
   end
 
-  context "Namespace selector" do
-    context "with user namespace" do
+  context 'Namespace selector' do
+    context 'with user namespace' do
       before do
         visit new_project_path
       end
 
-      it "selects the user namespace" do
-        namespace = find("#project_namespace_id")
+      it 'selects the user namespace' do
+        namespace = find('#project_namespace_id')
 
         expect(namespace.text).to eq user.username
       end
     end
 
-    context "with group namespace" do
+    context 'with group namespace' do
       let(:group) { create(:group, :private, owner: user) }
 
       before do
@@ -49,13 +63,13 @@ feature "New project", feature: true do
         visit new_project_path(namespace_id: group.id)
       end
 
-      it "selects the group namespace" do
-        namespace = find("#project_namespace_id option[selected]")
+      it 'selects the group namespace' do
+        namespace = find('#project_namespace_id option[selected]')
 
         expect(namespace.text).to eq group.name
       end
 
-      context "on validation error" do
+      context 'on validation error' do
         before do
           fill_in('project_path', with: 'private-group-project')
           choose('Internal')
@@ -64,15 +78,15 @@ feature "New project", feature: true do
           expect(page).to have_css '.project-edit-errors .alert.alert-danger'
         end
 
-        it "selects the group namespace" do
-          namespace = find("#project_namespace_id option[selected]")
+        it 'selects the group namespace' do
+          namespace = find('#project_namespace_id option[selected]')
 
           expect(namespace.text).to eq group.name
         end
       end
     end
 
-    context "with subgroup namespace" do
+    context 'with subgroup namespace' do
       let(:group) { create(:group, :private, owner: user) }
       let(:subgroup) { create(:group, parent: group) }
 
@@ -81,8 +95,8 @@ feature "New project", feature: true do
         visit new_project_path(namespace_id: subgroup.id)
       end
 
-      it "selects the group namespace" do
-        namespace = find("#project_namespace_id option[selected]")
+      it 'selects the group namespace' do
+        namespace = find('#project_namespace_id option[selected]')
 
         expect(namespace.text).to eq subgroup.full_path
       end
@@ -94,10 +108,45 @@ feature "New project", feature: true do
       visit new_project_path
     end
 
-    it 'does not autocomplete sensitive git repo URL' do
-      autocomplete = find('#project_import_url')['autocomplete']
+    context 'from git repository url' do
+      before do
+        first('.import_git').click
+      end
+
+      it 'does not autocomplete sensitive git repo URL' do
+        autocomplete = find('#project_import_url')['autocomplete']
+
+        expect(autocomplete).to eq('off')
+      end
+
+      it 'shows import instructions' do
+        git_import_instructions = first('.js-toggle-content')
 
-      expect(autocomplete).to eq('off')
+        expect(git_import_instructions).to be_visible
+        expect(git_import_instructions).to have_content 'Git repository URL'
+      end
+    end
+
+    context 'from GitHub' do
+      before do
+        first('.import_github').click
+      end
+
+      it 'shows import instructions' do
+        expect(page).to have_content('Import Projects from GitHub')
+        expect(current_path).to eq new_import_github_path
+      end
+    end
+
+    context 'from Google Code' do
+      before do
+        first('.import_google_code').click
+      end
+
+      it 'shows import instructions' do
+        expect(page).to have_content('Import projects from Google Code')
+        expect(current_path).to eq new_import_google_code_path
+      end
     end
   end
 end
diff --git a/spec/features/projects/no_password_spec.rb b/spec/features/projects/no_password_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..53ac18fa7ccbee4149690a5a383b1866c2dad8c4
--- /dev/null
+++ b/spec/features/projects/no_password_spec.rb
@@ -0,0 +1,69 @@
+require 'spec_helper'
+
+feature 'No Password Alert' do
+  let(:project) { create(:project, namespace: user.namespace) }
+
+  context 'with internal auth enabled' do
+    before do
+      sign_in(user)
+      visit project_path(project)
+    end
+
+    context 'when user has a password' do
+      let(:user) { create(:user) }
+
+      it 'shows no alert' do
+        expect(page).not_to have_content "You won't be able to pull or push project code via HTTP until you set a password on your account"
+      end
+    end
+
+    context 'when user has password automatically set' do
+      let(:user) { create(:user, password_automatically_set: true) }
+
+      it 'shows a password alert' do
+        expect(page).to have_content "You won't be able to pull or push project code via HTTP until you set a password on your account"
+      end
+    end
+  end
+
+  context 'with internal auth disabled' do
+    let(:user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'saml') }
+
+    before do
+      stub_application_setting(signin_enabled?: false)
+      stub_omniauth_saml_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'], providers: [mock_saml_config])
+    end
+
+    context 'when user has no personal access tokens' do
+      it 'has a personal access token alert' do
+        gitlab_sign_in_via('saml', user, 'my-uid')
+        visit project_path(project)
+
+        expect(page).to have_content "You won't be able to pull or push project code via HTTP until you create a personal access token on your account"
+      end
+    end
+
+    context 'when user has a personal access token' do
+      it 'shows no alert' do
+        create(:personal_access_token, user: user)
+        gitlab_sign_in_via('saml', user, 'my-uid')
+        visit project_path(project)
+
+        expect(page).not_to have_content "You won't be able to pull or push project code via HTTP until you create a personal access token on your account"
+      end
+    end
+  end
+
+  context 'when user is ldap user' do
+    let(:user) { create(:omniauth_user, password_automatically_set: true) }
+
+    before do
+      sign_in(user)
+      visit project_path(project)
+    end
+
+    it 'shows no alert' do
+      expect(page).not_to have_content "You won't be able to pull or push project code via HTTP until you"
+    end
+  end
+end
diff --git a/spec/features/projects/pages_spec.rb b/spec/features/projects/pages_spec.rb
index 11793c0f3037f1b3bee3677fceffc96ba30d563d..e2cc38e276f420ecbdb758489653ef49b7fc2b06 100644
--- a/spec/features/projects/pages_spec.rb
+++ b/spec/features/projects/pages_spec.rb
@@ -10,12 +10,12 @@ feature 'Pages', feature: true do
 
     project.team << [user, role]
 
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   shared_examples 'no pages deployed' do
     scenario 'does not see anything to destroy' do
-      visit namespace_project_pages_path(project.namespace, project)
+      visit project_pages_path(project)
 
       expect(page).not_to have_link('Remove pages')
       expect(page).not_to have_text('Only the project owner can remove pages')
@@ -33,7 +33,7 @@ feature 'Pages', feature: true do
       end
 
       scenario 'sees "Remove pages" link' do
-        visit namespace_project_pages_path(project.namespace, project)
+        visit project_pages_path(project)
 
         expect(page).to have_link('Remove pages')
       end
@@ -49,7 +49,7 @@ feature 'Pages', feature: true do
       end
 
       scenario 'sees "Only the project owner can remove pages" text' do
-        visit namespace_project_pages_path(project.namespace, project)
+        visit project_pages_path(project)
 
         expect(page).to have_text('Only the project owner can remove pages')
       end
diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb
index 2d43f7a10bc8ed93ef153394e97d8a444fbaf40e..d8bb7ca9a83e0c9b0bca2f7af87afed9c564333a 100644
--- a/spec/features/projects/pipeline_schedules_spec.rb
+++ b/spec/features/projects/pipeline_schedules_spec.rb
@@ -12,7 +12,7 @@ feature 'Pipeline Schedules', :feature do
   before do
     project.add_master(user)
 
-    login_as(user)
+    gitlab_sign_in(user)
     visit_page
   end
 
@@ -135,15 +135,15 @@ feature 'Pipeline Schedules', :feature do
   end
 
   def visit_new_pipeline_schedule
-    visit new_namespace_project_pipeline_schedule_path(project.namespace, project, pipeline_schedule)
+    visit new_project_pipeline_schedule_path(project, pipeline_schedule)
   end
 
   def edit_pipeline_schedule
-    visit edit_namespace_project_pipeline_schedule_path(project.namespace, project, pipeline_schedule)
+    visit edit_project_pipeline_schedule_path(project, pipeline_schedule)
   end
 
   def visit_pipelines_schedules
-    visit namespace_project_pipeline_schedules_path(project.namespace, project, scope: scope)
+    visit project_pipeline_schedules_path(project, scope: scope)
   end
 
   def select_timezone
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index 12c5ad45baf06ae196bc4259a4574723a281b011..bd6750d2208fcfc23c3580fb81b1b1e445a70c4d 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -1,13 +1,11 @@
 require 'spec_helper'
 
 describe 'Pipeline', :feature, :js do
-  include GitlabRoutingHelper
-
   let(:project) { create(:empty_project) }
   let(:user) { create(:user) }
 
   before do
-    login_as(user)
+    gitlab_sign_in(user)
     project.team << [user, :developer]
   end
 
@@ -48,7 +46,7 @@ describe 'Pipeline', :feature, :js do
     let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id, user: user) }
 
     before do
-      visit namespace_project_pipeline_path(project.namespace, project, pipeline)
+      visit project_pipeline_path(project, pipeline)
     end
 
     it 'shows the pipeline graph' do
@@ -194,7 +192,7 @@ describe 'Pipeline', :feature, :js do
     let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) }
 
     before do
-      visit builds_namespace_project_pipeline_path(project.namespace, project, pipeline)
+      visit builds_project_pipeline_path(project, pipeline)
     end
 
     it 'shows a list of jobs' do
@@ -266,7 +264,7 @@ describe 'Pipeline', :feature, :js do
   describe 'GET /:project/pipelines/:id/failures' do
     let(:project) { create(:project) }
     let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) }
-    let(:pipeline_failures_page) { failures_namespace_project_pipeline_path(project.namespace, project, pipeline) }
+    let(:pipeline_failures_page) { failures_project_pipeline_path(project, pipeline) }
     let!(:failed_build) { create(:ci_build, :failed, pipeline: pipeline) }
 
     context 'with failed build' do
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index db2d1a100a573dc77a478fd468e18fa6b2240ef8..a82a804e4c1892bd43adae1b31c9c42d0f43be94 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -7,7 +7,7 @@ describe 'Pipelines', :feature, :js do
     let(:user) { create(:user) }
 
     before do
-      login_as(user)
+      gitlab_sign_in(user)
       project.team << [user, :developer]
     end
 
@@ -51,7 +51,7 @@ describe 'Pipelines', :feature, :js do
 
       context 'header tabs' do
         before do
-          visit namespace_project_pipelines_path(project.namespace, project)
+          visit project_pipelines_path(project)
           wait_for_requests
         end
 
@@ -369,14 +369,14 @@ describe 'Pipelines', :feature, :js do
         end
 
         it 'should render pagination' do
-          visit namespace_project_pipelines_path(project.namespace, project)
+          visit project_pipelines_path(project)
           wait_for_requests
 
           expect(page).to have_selector('.gl-pagination')
         end
 
         it 'should render second page of pipelines' do
-          visit namespace_project_pipelines_path(project.namespace, project, page: '2')
+          visit project_pipelines_path(project, page: '2')
           wait_for_requests
 
           expect(page).to have_selector('.gl-pagination .page', count: 2)
@@ -405,7 +405,7 @@ describe 'Pipelines', :feature, :js do
 
         create(:generic_commit_status, pipeline: pipeline, stage: 'external', name: 'jenkins', stage_idx: 3)
 
-        visit namespace_project_pipeline_path(project.namespace, project, pipeline)
+        visit project_pipeline_path(project, pipeline)
         wait_for_requests
       end
 
@@ -440,7 +440,7 @@ describe 'Pipelines', :feature, :js do
       let(:project) { create(:project) }
 
       before do
-        visit new_namespace_project_pipeline_path(project.namespace, project)
+        visit new_project_pipeline_path(project)
       end
 
       context 'for valid commit', js: true do
@@ -479,7 +479,7 @@ describe 'Pipelines', :feature, :js do
       let(:project) { create(:project) }
 
       before do
-        visit new_namespace_project_pipeline_path(project.namespace, project)
+        visit new_project_pipeline_path(project)
       end
 
       describe 'new pipeline page' do
@@ -508,7 +508,7 @@ describe 'Pipelines', :feature, :js do
 
   context 'when user is not logged in' do
     before do
-      visit namespace_project_pipelines_path(project.namespace, project)
+      visit project_pipelines_path(project)
     end
 
     context 'when project is public' do
@@ -526,7 +526,7 @@ describe 'Pipelines', :feature, :js do
   end
 
   def visit_project_pipelines(**query)
-    visit namespace_project_pipelines_path(project.namespace, project, query)
+    visit project_pipelines_path(project, query)
     wait_for_requests
   end
 end
diff --git a/spec/features/projects/project_settings_spec.rb b/spec/features/projects/project_settings_spec.rb
index 2a9b32ea07ea6f15df3fd2953325dc1d3debf112..1f78f242399d1ae5e4ba57b54a91a61b536b83bc 100644
--- a/spec/features/projects/project_settings_spec.rb
+++ b/spec/features/projects/project_settings_spec.rb
@@ -7,12 +7,12 @@ describe 'Edit Project Settings', feature: true do
   let(:project) { create(:empty_project, namespace: user.namespace, path: 'gitlab', name: 'sample') }
 
   before do
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   describe 'Project settings section', js: true do
     it 'shows errors for invalid project name' do
-      visit edit_namespace_project_path(project.namespace, project)
+      visit edit_project_path(project)
       fill_in 'project_name_edit', with: 'foo&bar'
       click_button 'Save changes'
       expect(page).to have_field 'project_name_edit', with: 'foo&bar'
@@ -21,7 +21,7 @@ describe 'Edit Project Settings', feature: true do
     end
 
     it 'shows a successful notice when the project is updated' do
-      visit edit_namespace_project_path(project.namespace, project)
+      visit edit_project_path(project)
       fill_in 'project_name_edit', with: 'hello world'
       click_button 'Save changes'
       expect(page).to have_content "Project 'hello world' was successfully updated."
@@ -75,7 +75,7 @@ describe 'Edit Project Settings', feature: true do
       end
 
       specify 'the project is accessible via a redirect from the old path' do
-        old_path = namespace_project_path(project.namespace, project)
+        old_path = project_path(project)
         rename_project(project, path: 'bar')
         new_path = namespace_project_path(project.namespace, 'bar')
         visit old_path
@@ -85,7 +85,7 @@ describe 'Edit Project Settings', feature: true do
 
       context 'and a new project is added with the same path' do
         it 'overrides the redirect' do
-          old_path = namespace_project_path(project.namespace, project)
+          old_path = project_path(project)
           rename_project(project, path: 'bar')
           new_project = create(:empty_project, namespace: user.namespace, path: 'gitlabhq', name: 'quz')
           visit old_path
@@ -122,7 +122,7 @@ describe 'Edit Project Settings', feature: true do
     end
 
     specify 'the project is accessible via a redirect from the old path' do
-      old_path = namespace_project_path(project.namespace, project)
+      old_path = project_path(project)
       transfer_project(project, group)
       new_path = namespace_project_path(group, project)
       visit old_path
@@ -132,7 +132,7 @@ describe 'Edit Project Settings', feature: true do
 
     context 'and a new project is added with the same path' do
       it 'overrides the redirect' do
-        old_path = namespace_project_path(project.namespace, project)
+        old_path = project_path(project)
         transfer_project(project, group)
         new_project = create(:empty_project, namespace: user.namespace, path: 'gitlabhq', name: 'quz')
         visit old_path
@@ -144,7 +144,7 @@ describe 'Edit Project Settings', feature: true do
 end
 
 def rename_project(project, name: nil, path: nil)
-  visit edit_namespace_project_path(project.namespace, project)
+  visit edit_project_path(project)
   fill_in('project_name', with: name) if name
   fill_in('Path', with: path) if path
   click_button('Rename project')
@@ -153,7 +153,7 @@ def rename_project(project, name: nil, path: nil)
 end
 
 def transfer_project(project, namespace)
-  visit edit_namespace_project_path(project.namespace, project)
+  visit edit_project_path(project)
   select2(namespace.id, from: '#new_namespace_id')
   click_button('Transfer project')
   confirm_transfer_modal
diff --git a/spec/features/projects/ref_switcher_spec.rb b/spec/features/projects/ref_switcher_spec.rb
index 04414490571b14eae2a1ff0e0c82229ae30e5eab..342f083f25a8a442fe9c10389445164460d72081 100644
--- a/spec/features/projects/ref_switcher_spec.rb
+++ b/spec/features/projects/ref_switcher_spec.rb
@@ -6,8 +6,8 @@ feature 'Ref switcher', feature: true, js: true do
 
   before do
     project.team << [user, :master]
-    login_as(user)
-    visit namespace_project_tree_path(project.namespace, project, 'master')
+    gitlab_sign_in(user)
+    visit project_tree_path(project, 'master')
   end
 
   it 'allow user to change ref by enter key' do
diff --git a/spec/features/projects/services/jira_service_spec.rb b/spec/features/projects/services/jira_service_spec.rb
index c96d87e57083977c5da3455c55fb354b59428aeb..9e4f420689cde66296a3c76ecfcd18bbf8279e39 100644
--- a/spec/features/projects/services/jira_service_spec.rb
+++ b/spec/features/projects/services/jira_service_spec.rb
@@ -6,7 +6,11 @@ feature 'Setup Jira service', :feature, :js do
   let(:service) { project.create_jira_service }
 
   let(:url) { 'http://jira.example.com' }
-  let(:project_url) { 'http://username:password@jira.example.com/rest/api/2/project/GitLabProject' }
+
+  def stub_project_url
+    WebMock.stub_request(:get, 'http://jira.example.com/rest/api/2/project/GitLabProject')
+      .with(basic_auth: %w(username password))
+  end
 
   def fill_form(active = true)
     check 'Active' if active
@@ -20,15 +24,15 @@ feature 'Setup Jira service', :feature, :js do
 
   before do
     project.team << [user, :master]
-    login_as(user)
+    gitlab_sign_in(user)
 
-    visit namespace_project_settings_integrations_path(project.namespace, project)
+    visit project_settings_integrations_path(project)
   end
 
   describe 'user sets and activates Jira Service' do
     context 'when Jira connection test succeeds' do
       before do
-        WebMock.stub_request(:get, project_url)
+        stub_project_url
       end
 
       it 'activates the JIRA service' do
@@ -38,13 +42,13 @@ feature 'Setup Jira service', :feature, :js do
         wait_for_requests
 
         expect(page).to have_content('JIRA activated.')
-        expect(current_path).to eq(namespace_project_settings_integrations_path(project.namespace, project))
+        expect(current_path).to eq(project_settings_integrations_path(project))
       end
     end
 
     context 'when Jira connection test fails' do
       before do
-        WebMock.stub_request(:get, project_url).to_return(status: 401)
+        stub_project_url.to_return(status: 401)
       end
 
       it 'shows errors when some required fields are not filled in' do
@@ -72,7 +76,7 @@ feature 'Setup Jira service', :feature, :js do
         wait_for_requests
 
         expect(page).to have_content('JIRA activated.')
-        expect(current_path).to eq(namespace_project_settings_integrations_path(project.namespace, project))
+        expect(current_path).to eq(project_settings_integrations_path(project))
       end
     end
   end
@@ -85,7 +89,7 @@ feature 'Setup Jira service', :feature, :js do
         click_button('Save changes')
 
         expect(page).to have_content('JIRA settings saved, but not activated.')
-        expect(current_path).to eq(namespace_project_settings_integrations_path(project.namespace, project))
+        expect(current_path).to eq(project_settings_integrations_path(project))
       end
     end
   end
diff --git a/spec/features/projects/services/mattermost_slash_command_spec.rb b/spec/features/projects/services/mattermost_slash_command_spec.rb
index 1fe82222e59ba333ce1368a3e57a7b84155ef76e..aaa354903aad95c53531a2885278be9d10b45009 100644
--- a/spec/features/projects/services/mattermost_slash_command_spec.rb
+++ b/spec/features/projects/services/mattermost_slash_command_spec.rb
@@ -9,8 +9,8 @@ feature 'Setup Mattermost slash commands', :feature, :js do
   before do
     stub_mattermost_setting(enabled: mattermost_enabled)
     project.team << [user, :master]
-    login_as(user)
-    visit edit_namespace_project_service_path(project.namespace, project, service)
+    gitlab_sign_in(user)
+    visit edit_project_service_path(project, service)
   end
 
   describe 'user visits the mattermost slash command config page' do
@@ -30,7 +30,7 @@ feature 'Setup Mattermost slash commands', :feature, :js do
       fill_in 'service_token', with: token
       click_on 'Save changes'
 
-      expect(current_path).to eq(namespace_project_settings_integrations_path(project.namespace, project))
+      expect(current_path).to eq(project_settings_integrations_path(project))
       expect(page).to have_content('Mattermost slash commands settings saved, but not activated.')
     end
 
@@ -41,7 +41,7 @@ feature 'Setup Mattermost slash commands', :feature, :js do
       check 'service_active'
       click_on 'Save changes'
 
-      expect(current_path).to eq(namespace_project_settings_integrations_path(project.namespace, project))
+      expect(current_path).to eq(project_settings_integrations_path(project))
       expect(page).to have_content('Mattermost slash commands activated.')
     end
 
diff --git a/spec/features/projects/services/slack_service_spec.rb b/spec/features/projects/services/slack_service_spec.rb
index c0a4a1e4bf5196e14d18a012c211c7007c701722..5e3c3b00476d10edf5f5a97d367924cc3f14e32b 100644
--- a/spec/features/projects/services/slack_service_spec.rb
+++ b/spec/features/projects/services/slack_service_spec.rb
@@ -9,11 +9,11 @@ feature 'Projects > Slack service > Setup events', feature: true do
     service.fields
     service.update_attributes(push_channel: 1, issue_channel: 2, merge_request_channel: 3, note_channel: 4, tag_push_channel: 5, pipeline_channel: 6, wiki_page_channel: 7)
     project.team << [user, :master]
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   scenario 'user can filter events by channel' do
-    visit edit_namespace_project_service_path(project.namespace, project, service)
+    visit edit_project_service_path(project, service)
 
     expect(page.find_field("service_push_channel").value).to have_content '1'
     expect(page.find_field("service_issue_channel").value).to have_content '2'
diff --git a/spec/features/projects/services/slack_slash_command_spec.rb b/spec/features/projects/services/slack_slash_command_spec.rb
index f53b820c46062d95a53c66e45c50047aafa903a7..aaa775ce51f6586e9c661c224c35606fe00895b7 100644
--- a/spec/features/projects/services/slack_slash_command_spec.rb
+++ b/spec/features/projects/services/slack_slash_command_spec.rb
@@ -7,8 +7,8 @@ feature 'Slack slash commands', feature: true do
 
   background do
     project.team << [user, :master]
-    login_as(user)
-    visit edit_namespace_project_service_path(project.namespace, project, service)
+    gitlab_sign_in(user)
+    visit edit_project_service_path(project, service)
   end
 
   it 'shows a token placeholder' do
@@ -25,7 +25,7 @@ feature 'Slack slash commands', feature: true do
     fill_in 'service_token', with: 'token'
     click_on 'Save'
 
-    expect(current_path).to eq(namespace_project_settings_integrations_path(project.namespace, project))
+    expect(current_path).to eq(project_settings_integrations_path(project))
     expect(page).to have_content('Slack slash commands settings saved, but not activated.')
   end
 
@@ -34,7 +34,7 @@ feature 'Slack slash commands', feature: true do
     check 'service_active'
     click_on 'Save'
 
-    expect(current_path).to eq(namespace_project_settings_integrations_path(project.namespace, project))
+    expect(current_path).to eq(project_settings_integrations_path(project))
     expect(page).to have_content('Slack slash commands activated.')
   end
 
diff --git a/spec/features/projects/settings/integration_settings_spec.rb b/spec/features/projects/settings/integration_settings_spec.rb
index fbaea14a2bedd46a15794dd8c75c728e92c4591b..f708a3009f1aa25c3716e7de5279ec293dd62922 100644
--- a/spec/features/projects/settings/integration_settings_spec.rb
+++ b/spec/features/projects/settings/integration_settings_spec.rb
@@ -4,10 +4,10 @@ feature 'Integration settings', feature: true do
   let(:project) { create(:empty_project) }
   let(:user) { create(:user) }
   let(:role) { :developer }
-  let(:integrations_path) { namespace_project_settings_integrations_path(project.namespace, project) }
+  let(:integrations_path) { project_settings_integrations_path(project) }
 
   background do
-    login_as(user)
+    gitlab_sign_in(user)
     project.team << [user, role]
   end
 
@@ -109,7 +109,7 @@ feature 'Integration settings', feature: true do
 
       scenario 'show list of hook logs' do
         hook_log
-        visit edit_namespace_project_hook_path(project.namespace, project, hook)
+        visit edit_project_hook_path(project, hook)
 
         expect(page).to have_content('Recent Deliveries')
         expect(page).to have_content(hook_log.url)
@@ -117,7 +117,7 @@ feature 'Integration settings', feature: true do
 
       scenario 'show hook log details' do
         hook_log
-        visit edit_namespace_project_hook_path(project.namespace, project, hook)
+        visit edit_project_hook_path(project, hook)
         click_link 'View details'
 
         expect(page).to have_content("POST #{hook_log.url}")
@@ -129,11 +129,11 @@ feature 'Integration settings', feature: true do
         WebMock.stub_request(:post, hook.url)
 
         hook_log
-        visit edit_namespace_project_hook_path(project.namespace, project, hook)
+        visit edit_project_hook_path(project, hook)
         click_link 'View details'
         click_link 'Resend Request'
 
-        expect(current_path).to eq(edit_namespace_project_hook_path(project.namespace, project, hook))
+        expect(current_path).to eq(edit_project_hook_path(project, hook))
       end
     end
   end
diff --git a/spec/features/projects/settings/merge_requests_settings_spec.rb b/spec/features/projects/settings/merge_requests_settings_spec.rb
index 321af416c9146a32ff6c47c718e3c5592119f24e..451e2f3e04e0f1371c04747f8659a29822af06a4 100644
--- a/spec/features/projects/settings/merge_requests_settings_spec.rb
+++ b/spec/features/projects/settings/merge_requests_settings_spec.rb
@@ -1,14 +1,12 @@
 require 'spec_helper'
 
 feature 'Project settings > Merge Requests', feature: true, js: true do
-  include GitlabRoutingHelper
-
   let(:project) { create(:empty_project, :public) }
   let(:user) { create(:user) }
 
   background do
     project.team << [user, :master]
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   context 'when Merge Request and Pipelines are initially enabled' do
diff --git a/spec/features/projects/settings/pipelines_settings_spec.rb b/spec/features/projects/settings/pipelines_settings_spec.rb
index 035c57eaa47e1d14b72170ebf74401aef6053421..0d78feb2b93f24214e460fb1f473135f152adcb8 100644
--- a/spec/features/projects/settings/pipelines_settings_spec.rb
+++ b/spec/features/projects/settings/pipelines_settings_spec.rb
@@ -1,16 +1,14 @@
 require 'spec_helper'
 
 feature "Pipelines settings", feature: true do
-  include GitlabRoutingHelper
-
   let(:project) { create(:empty_project) }
   let(:user) { create(:user) }
   let(:role) { :developer }
 
   background do
-    login_as(user)
+    gitlab_sign_in(user)
     project.team << [user, role]
-    visit namespace_project_pipelines_settings_path(project.namespace, project)
+    visit project_pipelines_settings_path(project)
   end
 
   context 'for developer' do
diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb
index 4cc38c5286e2d264d5199e4f41bf03ba16fe0df0..9cc04925a0aafeab00804cbc6df29dd5c86d98a4 100644
--- a/spec/features/projects/settings/repository_settings_spec.rb
+++ b/spec/features/projects/settings/repository_settings_spec.rb
@@ -7,14 +7,14 @@ feature 'Repository settings', feature: true do
 
   background do
     project.team << [user, role]
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   context 'for developer' do
     given(:role) { :developer }
 
     scenario 'is not allowed to view' do
-      visit namespace_project_settings_repository_path(project.namespace, project)
+      visit project_settings_repository_path(project)
 
       expect(page.status_code).to eq(404)
     end
@@ -32,7 +32,7 @@ feature 'Repository settings', feature: true do
         project.deploy_keys << private_deploy_key
         project.deploy_keys << public_deploy_key
 
-        visit namespace_project_settings_repository_path(project.namespace, project)
+        visit project_settings_repository_path(project)
 
         expect(page.status_code).to eq(200)
         expect(page).to have_content('private_deploy_key')
@@ -40,7 +40,7 @@ feature 'Repository settings', feature: true do
       end
 
       scenario 'add a new deploy key' do
-        visit namespace_project_settings_repository_path(project.namespace, project)
+        visit project_settings_repository_path(project)
 
         fill_in 'deploy_key_title', with: 'new_deploy_key'
         fill_in 'deploy_key_key', with: new_ssh_key
@@ -53,7 +53,24 @@ feature 'Repository settings', feature: true do
 
       scenario 'edit an existing deploy key' do
         project.deploy_keys << private_deploy_key
-        visit namespace_project_settings_repository_path(project.namespace, project)
+        visit project_settings_repository_path(project)
+
+        find('li', text: private_deploy_key.title).click_link('Edit')
+
+        fill_in 'deploy_key_title', with: 'updated_deploy_key'
+        check 'deploy_key_can_push'
+        click_button 'Save changes'
+
+        expect(page).to have_content('updated_deploy_key')
+        expect(page).to have_content('Write access allowed')
+      end
+
+      scenario 'edit a deploy key from projects user has access to' do
+        project2 = create(:project_empty_repo)
+        project2.team << [user, role]
+        project2.deploy_keys << private_deploy_key
+
+        visit project_settings_repository_path(project)
 
         find('li', text: private_deploy_key.title).click_link('Edit')
 
@@ -67,7 +84,7 @@ feature 'Repository settings', feature: true do
 
       scenario 'remove an existing deploy key' do
         project.deploy_keys << private_deploy_key
-        visit namespace_project_settings_repository_path(project.namespace, project)
+        visit project_settings_repository_path(project)
 
         find('li', text: private_deploy_key.title).click_button('Remove')
 
diff --git a/spec/features/projects/settings/visibility_settings_spec.rb b/spec/features/projects/settings/visibility_settings_spec.rb
index fac4506bdf65cb3fd589706c52af09f8e409e573..a9a6441d4e8241db94e3df15e5921b003562ca6e 100644
--- a/spec/features/projects/settings/visibility_settings_spec.rb
+++ b/spec/features/projects/settings/visibility_settings_spec.rb
@@ -6,8 +6,8 @@ feature 'Visibility settings', feature: true, js: true do
 
   context 'as owner' do
     before do
-      login_as(user)
-      visit edit_namespace_project_path(project.namespace, project)
+      gitlab_sign_in(user)
+      visit edit_project_path(project)
     end
 
     scenario 'project visibility select is available' do
@@ -32,8 +32,8 @@ feature 'Visibility settings', feature: true, js: true do
 
     before do
       project.team << [master_user, :master]
-      login_as(master_user)
-      visit edit_namespace_project_path(project.namespace, project)
+      gitlab_sign_in(master_user)
+      visit edit_project_path(project)
     end
 
     scenario 'project visibility is locked' do
diff --git a/spec/features/projects/shortcuts_spec.rb b/spec/features/projects/shortcuts_spec.rb
index 54aa9c66a08cf7ad14b781d4736826fca06398ad..682bea87c8a835f206e6e382dbf0de4b51feeefd 100644
--- a/spec/features/projects/shortcuts_spec.rb
+++ b/spec/features/projects/shortcuts_spec.rb
@@ -7,8 +7,8 @@ feature 'Project shortcuts', feature: true do
   describe 'On a project', js: true do
     before do
       project.team << [user, :master]
-      login_as user
-      visit namespace_project_path(project.namespace, project)
+      gitlab_sign_in user
+      visit project_path(project)
     end
 
     describe 'pressing "i"' do
diff --git a/spec/features/projects/snippets/create_snippet_spec.rb b/spec/features/projects/snippets/create_snippet_spec.rb
index 5ac1ca45c741f4eb33a77d26cd1816d92c85d066..37c11c0e88d1bdc83622be847f01c76f7862f7d2 100644
--- a/spec/features/projects/snippets/create_snippet_spec.rb
+++ b/spec/features/projects/snippets/create_snippet_spec.rb
@@ -17,9 +17,9 @@ feature 'Create Snippet', :js, feature: true do
   context 'when a user is authenticated' do
     before do
       project.team << [user, :master]
-      login_as(user)
+      gitlab_sign_in(user)
 
-      visit namespace_project_snippets_path(project.namespace, project)
+      visit project_snippets_path(project)
 
       click_on('New snippet')
     end
@@ -77,7 +77,7 @@ feature 'Create Snippet', :js, feature: true do
     it 'shows a public snippet on the index page but not the New snippet button' do
       snippet = create(:project_snippet, :public, project: project)
 
-      visit namespace_project_snippets_path(project.namespace, project)
+      visit project_snippets_path(project)
 
       expect(page).to have_content(snippet.title)
       expect(page).not_to have_content('New snippet')
diff --git a/spec/features/projects/snippets/show_spec.rb b/spec/features/projects/snippets/show_spec.rb
index b844e60e5d5c4cef15bafc162dcd6b8a4c63adb0..d401d09497f1e1215696ba820f0c5f0a46ad63b5 100644
--- a/spec/features/projects/snippets/show_spec.rb
+++ b/spec/features/projects/snippets/show_spec.rb
@@ -7,7 +7,7 @@ feature 'Project snippet', :js, feature: true do
 
   before do
     project.team << [user, :master]
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   context 'Ruby file' do
@@ -15,7 +15,7 @@ feature 'Project snippet', :js, feature: true do
     let(:content) { project.repository.blob_at('master', 'files/ruby/popen.rb').data }
 
     before do
-      visit namespace_project_snippet_path(project.namespace, project, snippet)
+      visit project_snippet_path(project, snippet)
 
       wait_for_requests
     end
@@ -46,7 +46,7 @@ feature 'Project snippet', :js, feature: true do
 
     context 'visiting directly' do
       before do
-        visit namespace_project_snippet_path(project.namespace, project, snippet)
+        visit project_snippet_path(project, snippet)
 
         wait_for_requests
       end
@@ -118,7 +118,7 @@ feature 'Project snippet', :js, feature: true do
 
     context 'visiting with a line number anchor' do
       before do
-        visit namespace_project_snippet_path(project.namespace, project, snippet, anchor: 'L1')
+        visit project_snippet_path(project, snippet, anchor: 'L1')
 
         wait_for_requests
       end
diff --git a/spec/features/projects/snippets_spec.rb b/spec/features/projects/snippets_spec.rb
index 18689c17fe93c7238bc3653e6e28aabdd6797835..8edef2eba1371fde8acb4dc1e822decdaaa1366e 100644
--- a/spec/features/projects/snippets_spec.rb
+++ b/spec/features/projects/snippets_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe 'Project snippets', feature: true do
+describe 'Project snippets', :js, feature: true do
   context 'when the project has snippets' do
     let(:project) { create(:empty_project, :public) }
     let!(:snippets) { create_list(:project_snippet, 2, :public, author: project.owner, project: project) }
@@ -10,7 +10,7 @@ describe 'Project snippets', feature: true do
       before do
         allow(Snippet).to receive(:default_per_page).and_return(1)
 
-        visit namespace_project_snippets_path(project.namespace, project)
+        visit project_snippets_path(project)
       end
 
       it_behaves_like 'paginated snippets'
@@ -18,7 +18,7 @@ describe 'Project snippets', feature: true do
 
     context 'list content' do
       it 'contains all project snippets' do
-        visit namespace_project_snippets_path(project.namespace, project)
+        visit project_snippets_path(project)
 
         expect(page).to have_selector('.snippet-row', count: 2)
 
@@ -26,5 +26,19 @@ describe 'Project snippets', feature: true do
         expect(page).to have_content(snippets[1].title)
       end
     end
+
+    context 'when submitting a note' do
+      before do
+        gitlab_sign_in :admin
+        visit project_snippet_path(project, snippets[0])
+      end
+
+      it 'should have autocomplete' do
+        find('#note_note').native.send_keys('')
+        fill_in 'note[note]', with: '@'
+
+        expect(page).to have_selector('.atwho-view')
+      end
+    end
   end
 end
diff --git a/spec/features/projects/sub_group_issuables_spec.rb b/spec/features/projects/sub_group_issuables_spec.rb
index e88907b801611216bc75a4d8dfb4a6ef3b002315..5bbad78d0bb0401e8a20b0d63d58a33d55b45d74 100644
--- a/spec/features/projects/sub_group_issuables_spec.rb
+++ b/spec/features/projects/sub_group_issuables_spec.rb
@@ -8,17 +8,17 @@ describe 'Subgroup Issuables', :feature, :js, :nested_groups do
 
   before do
     project.add_master(user)
-    login_as user
+    gitlab_sign_in user
   end
 
   it 'shows the full subgroup title when issues index page is empty' do
-    visit namespace_project_issues_path(project.namespace.to_param, project.to_param)
+    visit project_issues_path(project)
 
     expect_to_have_full_subgroup_title
   end
 
   it 'shows the full subgroup title when merge requests index page is empty' do
-    visit namespace_project_merge_requests_path(project.namespace.to_param, project.to_param)
+    visit project_merge_requests_path(project)
 
     expect_to_have_full_subgroup_title
   end
diff --git a/spec/features/projects/tags/download_buttons_spec.rb b/spec/features/projects/tags/download_buttons_spec.rb
index dd93d25c2c6b8e15e57749a2767447a8f8c76579..186876e454fd8c5b1f1211e2b057e0c0344f80f4 100644
--- a/spec/features/projects/tags/download_buttons_spec.rb
+++ b/spec/features/projects/tags/download_buttons_spec.rb
@@ -23,20 +23,18 @@ feature 'Download buttons in tags page', feature: true do
   end
 
   background do
-    login_as(user)
+    gitlab_sign_in(user)
     project.team << [user, role]
   end
 
   describe 'when checking tags' do
     context 'with artifacts' do
       before do
-        visit namespace_project_tags_path(project.namespace, project)
+        visit project_tags_path(project)
       end
 
       scenario 'shows download artifacts button' do
-        href = latest_succeeded_namespace_project_artifacts_path(
-          project.namespace, project, "#{tag}/download",
-          job: 'build')
+        href = latest_succeeded_project_artifacts_path(project, "#{tag}/download", job: 'build')
 
         expect(page).to have_link "Download '#{build.name}'", href: href
       end
diff --git a/spec/features/projects/tree/rss_spec.rb b/spec/features/projects/tree/rss_spec.rb
index 9bf59c4139c9d47a592ddb198dc50281f44c355b..4583374c931e13fbe59d51d302038c63911c6eee 100644
--- a/spec/features/projects/tree/rss_spec.rb
+++ b/spec/features/projects/tree/rss_spec.rb
@@ -2,13 +2,13 @@ require 'spec_helper'
 
 feature 'Project Tree RSS' do
   let(:project) { create(:project, :repository, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
-  let(:path) { namespace_project_tree_path(project.namespace, project, :master) }
+  let(:path) { project_tree_path(project, :master) }
 
   context 'when signed in' do
     before do
       user = create(:user)
       project.team << [user, :developer]
-      login_as(user)
+      gitlab_sign_in(user)
       visit path
     end
 
diff --git a/spec/features/projects/user_create_dir_spec.rb b/spec/features/projects/user_create_dir_spec.rb
index aeb7e0b7c33aa13ee7f4f1984d23c53604c0e23c..01f288934bfaad2a6c5aaf8d9b83828901b37852 100644
--- a/spec/features/projects/user_create_dir_spec.rb
+++ b/spec/features/projects/user_create_dir_spec.rb
@@ -6,9 +6,9 @@ feature 'New directory creation', feature: true, js: true do
   given(:project) { create(:project) }
 
   background do
-    login_as(user)
+    gitlab_sign_in(user)
     project.team << [user, role]
-    visit namespace_project_tree_path(project.namespace, project, 'master')
+    visit project_tree_path(project, 'master')
     open_new_directory_modal
     fill_in 'dir_name', with: 'new_directory'
   end
@@ -51,7 +51,7 @@ feature 'New directory creation', feature: true, js: true do
       expect(page).to have_content 'New Merge Request'
       expect(page).to have_content "From #{new_branch_name} into master"
       expect(page).to have_content 'Add new directory'
-      expect(current_path).to eq(new_namespace_project_merge_request_path(project.namespace, project))
+      expect(current_path).to eq(project_new_merge_request_path(project))
     end
   end
 end
diff --git a/spec/features/projects/user_creates_project_spec.rb b/spec/features/projects/user_creates_project_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1c3791f63ac2efd4b0cc590ae9327a59fa08d090
--- /dev/null
+++ b/spec/features/projects/user_creates_project_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+feature 'User creates a project', js: true do
+  let(:user) { create(:user) }
+
+  before do
+    sign_in(user)
+    create(:personal_key, user: user)
+    visit(new_project_path)
+  end
+
+  it 'creates a new project' do
+    fill_in(:project_path, with: 'Empty')
+
+    page.within('#content-body') do
+      click_button('Create project')
+    end
+
+    project = Project.last
+
+    expect(current_path).to eq(project_path(project))
+    expect(page).to have_content('Empty')
+    expect(page).to have_content('git init')
+    expect(page).to have_content('git remote')
+    expect(page).to have_content(project.url_to_repo)
+  end
+end
diff --git a/spec/features/projects/view_on_env_spec.rb b/spec/features/projects/view_on_env_spec.rb
index 640f1376548696e515c86dc1ab89e1a7f61d1897..0c06aa25c06355dad80d07ab58d81e987d3059de 100644
--- a/spec/features/projects/view_on_env_spec.rb
+++ b/spec/features/projects/view_on_env_spec.rb
@@ -50,9 +50,9 @@ describe 'View on environment', js: true do
         let(:merge_request) { create(:merge_request, :simple, source_project: project, source_branch: branch_name) }
 
         before do
-          login_as(user)
+          gitlab_sign_in(user)
 
-          visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
+          visit diffs_project_merge_request_path(project, merge_request)
 
           wait_for_requests
         end
@@ -66,9 +66,9 @@ describe 'View on environment', js: true do
 
       context 'when visiting a comparison for the branch' do
         before do
-          login_as(user)
+          gitlab_sign_in(user)
 
-          visit namespace_project_compare_path(project.namespace, project, from: 'master', to: branch_name)
+          visit project_compare_path(project, from: 'master', to: branch_name)
 
           wait_for_requests
         end
@@ -80,9 +80,9 @@ describe 'View on environment', js: true do
 
       context 'when visiting a comparison for the commit' do
         before do
-          login_as(user)
+          gitlab_sign_in(user)
 
-          visit namespace_project_compare_path(project.namespace, project, from: 'master', to: sha)
+          visit project_compare_path(project, from: 'master', to: sha)
 
           wait_for_requests
         end
@@ -94,9 +94,9 @@ describe 'View on environment', js: true do
 
       context 'when visiting a blob on the branch' do
         before do
-          login_as(user)
+          gitlab_sign_in(user)
 
-          visit namespace_project_blob_path(project.namespace, project, File.join(branch_name, file_path))
+          visit project_blob_path(project, File.join(branch_name, file_path))
 
           wait_for_requests
         end
@@ -108,9 +108,9 @@ describe 'View on environment', js: true do
 
       context 'when visiting a blob on the commit' do
         before do
-          login_as(user)
+          gitlab_sign_in(user)
 
-          visit namespace_project_blob_path(project.namespace, project, File.join(sha, file_path))
+          visit project_blob_path(project, File.join(sha, file_path))
 
           wait_for_requests
         end
@@ -122,9 +122,9 @@ describe 'View on environment', js: true do
 
       context 'when visiting the commit' do
         before do
-          login_as(user)
+          gitlab_sign_in(user)
 
-          visit namespace_project_commit_path(project.namespace, project, sha)
+          visit project_commit_path(project, sha)
 
           wait_for_requests
         end
diff --git a/spec/features/projects/wiki/markdown_preview_spec.rb b/spec/features/projects/wiki/markdown_preview_spec.rb
index 94f6bb167309f1046007d049f12bd783a3097387..d79ab809c7df0b683eb6c38b8bee5f2182bd6c0a 100644
--- a/spec/features/projects/wiki/markdown_preview_spec.rb
+++ b/spec/features/projects/wiki/markdown_preview_spec.rb
@@ -16,9 +16,9 @@ feature 'Projects > Wiki > User previews markdown changes', feature: true, js: t
     project.team << [user, :master]
     WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute
 
-    login_as(user)
+    gitlab_sign_in(user)
 
-    visit namespace_project_path(project.namespace, project)
+    visit project_path(project)
     find('.shortcuts-wiki').trigger('click')
   end
 
diff --git a/spec/features/projects/wiki/shortcuts_spec.rb b/spec/features/projects/wiki/shortcuts_spec.rb
index c1f6b0cce3bb6c97ce41fd2913d2444767a73fc6..d189f84da0ee43004a12024f91414b94c866e1d2 100644
--- a/spec/features/projects/wiki/shortcuts_spec.rb
+++ b/spec/features/projects/wiki/shortcuts_spec.rb
@@ -8,8 +8,8 @@ feature 'Wiki shortcuts', :feature, :js do
   end
 
   before do
-    login_as(user)
-    visit namespace_project_wiki_path(project.namespace, project, wiki_page)
+    gitlab_sign_in(user)
+    visit project_wiki_path(project, wiki_page)
   end
 
   scenario 'Visit edit wiki page using "e" keyboard shortcut' do
diff --git a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
index 8912d5758785c9e67044681863a92d7562596de8..86b31057a556bd2841e70badb8e1d264857f1bb0 100644
--- a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
@@ -5,9 +5,9 @@ feature 'Projects > Wiki > User creates wiki page', js: true, feature: true do
 
   background do
     project.team << [user, :master]
-    login_as(user)
+    gitlab_sign_in(user)
 
-    visit namespace_project_path(project.namespace, project)
+    visit project_path(project)
     find('.shortcuts-wiki').trigger('click')
   end
 
@@ -133,6 +133,22 @@ feature 'Projects > Wiki > User creates wiki page', js: true, feature: true do
           expect(page).to have_content('My awesome wiki!')
         end
       end
+
+      scenario 'content has autocomplete', :js do
+        click_link 'New page'
+
+        page.within '#modal-new-wiki' do
+          fill_in :new_wiki_path, with: 'test-autocomplete'
+          click_button 'Create page'
+        end
+
+        page.within '.wiki-form' do
+          find('#wiki_content').native.send_keys('')
+          fill_in :wiki_content, with: '@'
+        end
+
+        expect(page).to have_selector('.atwho-view')
+      end
     end
   end
 
diff --git a/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb b/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb
index 95826e7e5be0cc902fbf78fa817df8dff7e55bce..749721b97ebaf94f4ce6abeabfff05654eb04894 100644
--- a/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb
@@ -13,11 +13,11 @@ describe 'Projects > Wiki > User views Git access wiki page', :feature do
   end
 
   before do
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   scenario 'Visit Wiki Page Current Commit' do
-    visit namespace_project_wiki_path(project.namespace, project, wiki_page)
+    visit project_wiki_path(project, wiki_page)
 
     click_link 'Clone repository'
     expect(page).to have_text("Clone repository #{project.wiki.path_with_namespace}")
diff --git a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
index 86cf520ea80ecf634d3cb7f4264ad33ac5527c59..3b9f7ff96fb6776f888b4403b9b13971463ee4b1 100644
--- a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
@@ -5,11 +5,10 @@ feature 'Projects > Wiki > User updates wiki page', feature: true do
 
   background do
     project.team << [user, :master]
-    login_as(user)
-
-    visit namespace_project_path(project.namespace, project)
     WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute
-    click_link 'Wiki'
+    gitlab_sign_in(user)
+
+    visit project_wikis_path(project)
   end
 
   context 'in the user namespace' do
@@ -42,6 +41,15 @@ feature 'Projects > Wiki > User updates wiki page', feature: true do
         expect(page).to have_content('Content can\'t be blank')
         expect(find('textarea#wiki_content').value).to eq ''
       end
+
+      scenario 'content has autocomplete', :js do
+        click_link 'Edit'
+
+        find('#wiki_content').native.send_keys('')
+        fill_in :wiki_content, with: '@'
+
+        expect(page).to have_selector('.atwho-view')
+      end
     end
   end
 
diff --git a/spec/features/projects/wiki/user_views_project_wiki_page_spec.rb b/spec/features/projects/wiki/user_views_project_wiki_page_spec.rb
index c17e06612de383f645c8efaeaa0233bc97718ea6..8e3912d994e2915d1c33031166c510bd4c7b3813 100644
--- a/spec/features/projects/wiki/user_views_project_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_views_project_wiki_page_spec.rb
@@ -15,7 +15,7 @@ feature 'Projects > Wiki > User views the wiki page', feature: true do
 
   background do
     project.team << [user, :master]
-    login_as(user)
+    gitlab_sign_in(user)
     WikiPages::UpdateService.new(
       project,
       user,
@@ -26,18 +26,13 @@ feature 'Projects > Wiki > User views the wiki page', feature: true do
   end
 
   scenario 'Visit Wiki Page Current Commit' do
-    visit namespace_project_wiki_path(project.namespace, project, wiki_page)
+    visit project_wiki_path(project, wiki_page)
 
     expect(page).to have_selector('a.btn', text: 'Edit')
   end
 
   scenario 'Visit Wiki Page Historical Commit' do
-    visit namespace_project_wiki_path(
-      project.namespace,
-      project,
-      wiki_page,
-      version_id: old_page_version_id
-    )
+    visit project_wiki_path(project, wiki_page, version_id: old_page_version_id)
 
     expect(page).not_to have_selector('a.btn', text: 'Edit')
   end
diff --git a/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb b/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb
index 20219f3cc9a9fb00f596de081e5f2e783eab6ade..a305d27c7ec37e8194abfdb3c08fef57c2185d16 100644
--- a/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb
+++ b/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb
@@ -5,7 +5,7 @@ describe 'Projects > Wiki > User views wiki in project page', feature: true do
 
   before do
     project.team << [user, :master]
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   context 'when repository is disabled for project' do
@@ -27,14 +27,10 @@ describe 'Projects > Wiki > User views wiki in project page', feature: true do
       end
 
       it 'displays the correct URL for the link' do
-        visit namespace_project_path(project.namespace, project)
+        visit project_path(project)
         expect(page).to have_link(
           'some link',
-          href: namespace_project_wiki_path(
-            project.namespace,
-            project,
-            'other-page'
-          )
+          href: project_wiki_path(project, 'other-page')
         )
       end
     end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index 060e19596aeb66443e4b9fd99fc1f2544c16a815..361e3a6d8e599a65e82beb4023cb124e26b3b567 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -3,10 +3,10 @@ require 'spec_helper'
 feature 'Project', feature: true do
   describe 'description' do
     let(:project) { create(:project, :repository) }
-    let(:path)    { namespace_project_path(project.namespace, project) }
+    let(:path)    { project_path(project) }
 
     before do
-      login_as(:admin)
+      gitlab_sign_in(:admin)
     end
 
     it 'parses Markdown' do
@@ -39,9 +39,9 @@ feature 'Project', feature: true do
     let(:project) { create(:empty_project, namespace: user.namespace) }
 
     before do
-      login_with user
+      gitlab_sign_in user
       create(:forked_project_link, forked_to_project: project)
-      visit edit_namespace_project_path(project.namespace, project)
+      visit edit_project_path(project)
     end
 
     it 'removes fork' do
@@ -60,9 +60,9 @@ feature 'Project', feature: true do
     let(:project) { create(:empty_project, namespace: user.namespace, name: 'project1') }
 
     before do
-      login_with(user)
+      gitlab_sign_in(user)
       project.team << [user, :master]
-      visit edit_namespace_project_path(project.namespace, project)
+      visit edit_project_path(project)
     end
 
     it 'removes a project' do
@@ -79,9 +79,9 @@ feature 'Project', feature: true do
     let(:project) { create(:empty_project, namespace: user.namespace) }
 
     before do
-      login_with(user)
+      gitlab_sign_in(user)
       project.add_user(user, Gitlab::Access::MASTER)
-      visit namespace_project_path(project.namespace, project)
+      visit project_path(project)
     end
 
     it 'clicks toggle and shows dropdown', js: true do
@@ -98,10 +98,10 @@ feature 'Project', feature: true do
 
     context 'on issues page', js: true do
       before do
-        login_with(user)
+        gitlab_sign_in(user)
         project.add_user(user, Gitlab::Access::MASTER)
         project2.add_user(user, Gitlab::Access::MASTER)
-        visit namespace_project_issue_path(project.namespace, project, issue)
+        visit project_issue_path(project, issue)
       end
 
       it 'clicks toggle and shows dropdown' do
@@ -123,8 +123,8 @@ feature 'Project', feature: true do
 
     before do
       project.team << [user, :master]
-      login_as user
-      visit namespace_project_path(project.namespace, project)
+      gitlab_sign_in user
+      visit project_path(project)
     end
 
     it 'has working links to files' do
diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb
index aa9164dd979fc2f0444667f4d7fe580c766b842c..952eb6c364330c329e79e38b1943f9d2288cb88e 100644
--- a/spec/features/protected_branches_spec.rb
+++ b/spec/features/protected_branches_spec.rb
@@ -5,7 +5,7 @@ feature 'Protected Branches', feature: true, js: true do
   let(:project) { create(:project, :repository) }
 
   before do
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   def set_protected_branch_name(branch_name)
@@ -16,7 +16,7 @@ feature 'Protected Branches', feature: true, js: true do
 
   describe "explicit protected branches" do
     it "allows creating explicit protected branches" do
-      visit namespace_project_protected_branches_path(project.namespace, project)
+      visit project_protected_branches_path(project)
       set_protected_branch_name('some-branch')
       click_on "Protect"
 
@@ -29,7 +29,7 @@ feature 'Protected Branches', feature: true, js: true do
       commit = create(:commit, project: project)
       project.repository.add_branch(user, 'some-branch', commit.id)
 
-      visit namespace_project_protected_branches_path(project.namespace, project)
+      visit project_protected_branches_path(project)
       set_protected_branch_name('some-branch')
       click_on "Protect"
 
@@ -37,7 +37,7 @@ feature 'Protected Branches', feature: true, js: true do
     end
 
     it "displays an error message if the named branch does not exist" do
-      visit namespace_project_protected_branches_path(project.namespace, project)
+      visit project_protected_branches_path(project)
       set_protected_branch_name('some-branch')
       click_on "Protect"
 
@@ -47,7 +47,7 @@ feature 'Protected Branches', feature: true, js: true do
 
   describe "wildcard protected branches" do
     it "allows creating protected branches with a wildcard" do
-      visit namespace_project_protected_branches_path(project.namespace, project)
+      visit project_protected_branches_path(project)
       set_protected_branch_name('*-stable')
       click_on "Protect"
 
@@ -60,7 +60,7 @@ feature 'Protected Branches', feature: true, js: true do
       project.repository.add_branch(user, 'production-stable', 'master')
       project.repository.add_branch(user, 'staging-stable', 'master')
 
-      visit namespace_project_protected_branches_path(project.namespace, project)
+      visit project_protected_branches_path(project)
       set_protected_branch_name('*-stable')
       click_on "Protect"
 
@@ -72,11 +72,11 @@ feature 'Protected Branches', feature: true, js: true do
       project.repository.add_branch(user, 'staging-stable', 'master')
       project.repository.add_branch(user, 'development', 'master')
 
-      visit namespace_project_protected_branches_path(project.namespace, project)
+      visit project_protected_branches_path(project)
       set_protected_branch_name('*-stable')
       click_on "Protect"
 
-      visit namespace_project_protected_branches_path(project.namespace, project)
+      visit project_protected_branches_path(project)
       click_on "2 matching branches"
 
       within(".protected-branches-list") do
diff --git a/spec/features/protected_tags_spec.rb b/spec/features/protected_tags_spec.rb
index 63a2058577669ee94f1379251fb1ab18f4c19445..4ffd97fb221e3924d0daea49de1bb7d8e714e660 100644
--- a/spec/features/protected_tags_spec.rb
+++ b/spec/features/protected_tags_spec.rb
@@ -5,7 +5,7 @@ feature 'Projected Tags', feature: true, js: true do
   let(:project) { create(:project, :repository) }
 
   before do
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   def set_protected_tag_name(tag_name)
@@ -17,7 +17,7 @@ feature 'Projected Tags', feature: true, js: true do
 
   describe "explicit protected tags" do
     it "allows creating explicit protected tags" do
-      visit namespace_project_protected_tags_path(project.namespace, project)
+      visit project_protected_tags_path(project)
       set_protected_tag_name('some-tag')
       click_on "Protect"
 
@@ -30,7 +30,7 @@ feature 'Projected Tags', feature: true, js: true do
       commit = create(:commit, project: project)
       project.repository.add_tag(user, 'some-tag', commit.id)
 
-      visit namespace_project_protected_tags_path(project.namespace, project)
+      visit project_protected_tags_path(project)
       set_protected_tag_name('some-tag')
       click_on "Protect"
 
@@ -38,7 +38,7 @@ feature 'Projected Tags', feature: true, js: true do
     end
 
     it "displays an error message if the named tag does not exist" do
-      visit namespace_project_protected_tags_path(project.namespace, project)
+      visit project_protected_tags_path(project)
       set_protected_tag_name('some-tag')
       click_on "Protect"
 
@@ -48,7 +48,7 @@ feature 'Projected Tags', feature: true, js: true do
 
   describe "wildcard protected tags" do
     it "allows creating protected tags with a wildcard" do
-      visit namespace_project_protected_tags_path(project.namespace, project)
+      visit project_protected_tags_path(project)
       set_protected_tag_name('*-stable')
       click_on "Protect"
 
@@ -61,7 +61,7 @@ feature 'Projected Tags', feature: true, js: true do
       project.repository.add_tag(user, 'production-stable', 'master')
       project.repository.add_tag(user, 'staging-stable', 'master')
 
-      visit namespace_project_protected_tags_path(project.namespace, project)
+      visit project_protected_tags_path(project)
       set_protected_tag_name('*-stable')
       click_on "Protect"
 
@@ -73,11 +73,11 @@ feature 'Projected Tags', feature: true, js: true do
       project.repository.add_tag(user, 'staging-stable', 'master')
       project.repository.add_tag(user, 'development', 'master')
 
-      visit namespace_project_protected_tags_path(project.namespace, project)
+      visit project_protected_tags_path(project)
       set_protected_tag_name('*-stable')
       click_on "Protect"
 
-      visit namespace_project_protected_tags_path(project.namespace, project)
+      visit project_protected_tags_path(project)
       click_on "2 matching tags"
 
       within(".protected-tags-list") do
diff --git a/spec/features/reportable_note/commit_spec.rb b/spec/features/reportable_note/commit_spec.rb
index 39b1c4acf52a7f5cb8ef644a4697978044e4acc0..2486f779753bc391a7750aee3e2b1d552c97d473 100644
--- a/spec/features/reportable_note/commit_spec.rb
+++ b/spec/features/reportable_note/commit_spec.rb
@@ -8,14 +8,14 @@ describe 'Reportable note on commit', :feature, :js do
 
   before do
     project.add_master(user)
-    login_as user
+    gitlab_sign_in(user)
   end
 
   context 'a normal note' do
     let!(:note) { create(:note_on_commit, commit_id: sample_commit.id, project: project) }
 
     before do
-      visit namespace_project_commit_path(project.namespace, project, sample_commit.id)
+      visit project_commit_path(project, sample_commit.id)
     end
 
     it_behaves_like 'reportable note'
@@ -25,7 +25,7 @@ describe 'Reportable note on commit', :feature, :js do
     let!(:note) { create(:diff_note_on_commit, commit_id: sample_commit.id, project: project) }
 
     before do
-      visit namespace_project_commit_path(project.namespace, project, sample_commit.id)
+      visit project_commit_path(project, sample_commit.id)
     end
 
     it_behaves_like 'reportable note'
diff --git a/spec/features/reportable_note/issue_spec.rb b/spec/features/reportable_note/issue_spec.rb
index 5f526818994b79af5655a991cdfcee994bb71d9d..d283c2d3c8f51842b161cbf2ce2c8b7a4d3daa5c 100644
--- a/spec/features/reportable_note/issue_spec.rb
+++ b/spec/features/reportable_note/issue_spec.rb
@@ -8,9 +8,9 @@ describe 'Reportable note on issue', :feature, :js do
 
   before do
     project.add_master(user)
-    login_as user
+    gitlab_sign_in(user)
 
-    visit namespace_project_issue_path(project.namespace, project, issue)
+    visit project_issue_path(project, issue)
   end
 
   it_behaves_like 'reportable note'
diff --git a/spec/features/reportable_note/merge_request_spec.rb b/spec/features/reportable_note/merge_request_spec.rb
index 6d053d26626f16701f59d848477cacc4a8bd7538..fe25c894b858e4148006aa090df7a7f2759d02ff 100644
--- a/spec/features/reportable_note/merge_request_spec.rb
+++ b/spec/features/reportable_note/merge_request_spec.rb
@@ -7,9 +7,9 @@ describe 'Reportable note on merge request', :feature, :js do
 
   before do
     project.add_master(user)
-    login_as user
+    gitlab_sign_in(user)
 
-    visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+    visit project_merge_request_path(project, merge_request)
   end
 
   context 'a normal note' do
diff --git a/spec/features/reportable_note/snippets_spec.rb b/spec/features/reportable_note/snippets_spec.rb
index 3f1e0cf9097108066978a48591baf49c0f03ab6e..b3044d3d04817e8e90ce0bedeac22af14317d205 100644
--- a/spec/features/reportable_note/snippets_spec.rb
+++ b/spec/features/reportable_note/snippets_spec.rb
@@ -6,7 +6,7 @@ describe 'Reportable note on snippets', :feature, :js do
 
   before do
     project.add_master(user)
-    login_as user
+    gitlab_sign_in(user)
   end
 
   describe 'on project snippet' do
@@ -14,18 +14,7 @@ describe 'Reportable note on snippets', :feature, :js do
     let!(:note) { create(:note_on_project_snippet, noteable: snippet, project: project) }
 
     before do
-      visit namespace_project_snippet_path(project.namespace, project, snippet)
-    end
-
-    it_behaves_like 'reportable note'
-  end
-
-  describe 'on personal snippet' do
-    let(:snippet) { create(:personal_snippet, :public, author: user) }
-    let!(:note) { create(:note_on_personal_snippet, noteable: snippet, author: user) }
-
-    before do
-      visit snippet_path(snippet)
+      visit project_snippet_path(project, snippet)
     end
 
     it_behaves_like 'reportable note'
diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb
index e87d52f5c8fe0e216d3f36f7468fe84b3394672a..00f59f8f197dd982f3da2d5eba76fcb96c84d6f0 100644
--- a/spec/features/runners_spec.rb
+++ b/spec/features/runners_spec.rb
@@ -1,12 +1,10 @@
 require 'spec_helper'
 
 describe "Runners" do
-  include GitlabRoutingHelper
-
   let(:user) { create(:user) }
 
   before do
-    login_as(user)
+    gitlab_sign_in(user)
   end
 
   describe "specific runners" do
@@ -124,7 +122,7 @@ describe "Runners" do
     end
 
     scenario 'user checks default configuration' do
-      visit namespace_project_runner_path(project.namespace, project, runner)
+      visit project_runner_path(project, runner)
 
       expect(page).to have_content 'Can run untagged jobs Yes'
     end
diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb
index 89d4f536b201713b7769fc17e7504949f2057a75..69b4219395583ea0d42f596a31b3b028d281f973 100644
--- a/spec/features/search_spec.rb
+++ b/spec/features/search_spec.rb
@@ -9,7 +9,7 @@ describe "Search", feature: true  do
   let!(:issue2) { create(:issue, project: project, author: user) }
 
   before do
-    login_with(user)
+    gitlab_sign_in(user)
     project.team << [user, :reporter]
     visit search_path
   end
@@ -88,7 +88,7 @@ describe "Search", feature: true  do
       end
 
       it 'finds comment' do
-        visit namespace_project_path(project.namespace, project)
+        visit project_path(project)
 
         page.within '.search' do
           fill_in 'search', with: note.note
@@ -111,7 +111,7 @@ describe "Search", feature: true  do
                     project: project)
       # Must visit project dashboard since global search won't search
       # everything (e.g. comments, snippets, etc.)
-      visit namespace_project_path(project.namespace, project)
+      visit project_path(project)
 
       page.within '.search' do
         fill_in 'search', with: note.note
@@ -125,7 +125,7 @@ describe "Search", feature: true  do
 
     it 'finds a commit' do
       project = create(:project, :repository) { |p| p.add_reporter(user) }
-      visit namespace_project_path(project.namespace, project)
+      visit project_path(project)
 
       page.within '.search' do
         fill_in 'search', with: 'add'
@@ -139,7 +139,7 @@ describe "Search", feature: true  do
 
     it 'finds a code' do
       project = create(:project, :repository) { |p| p.add_reporter(user) }
-      visit namespace_project_path(project.namespace, project)
+      visit project_path(project)
 
       page.within '.search' do
         fill_in 'search', with: 'application.js'
@@ -156,7 +156,7 @@ describe "Search", feature: true  do
 
   describe 'Right header search field', feature: true do
     it 'allows enter key to search', js: true do
-      visit namespace_project_path(project.namespace, project)
+      visit project_path(project)
       fill_in 'search', with: 'gitlab'
       find('#search').native.send_keys(:enter)
 
@@ -167,7 +167,7 @@ describe "Search", feature: true  do
 
     describe 'Search in project page' do
       before do
-        visit namespace_project_path(project.namespace, project)
+        visit project_path(project)
       end
 
       it 'shows top right search form' do
@@ -256,7 +256,7 @@ describe "Search", feature: true  do
 
       click_button 'Search'
 
-      expect(page).to have_current_path(namespace_project_commit_path(project.namespace, project, '6d394385cf567f80a8fd85055db1ab4c5295806f'))
+      expect(page).to have_current_path(project_commit_path(project, '6d394385cf567f80a8fd85055db1ab4c5295806f'))
     end
 
     it 'redirects to single commit regardless of query case' do
@@ -264,7 +264,7 @@ describe "Search", feature: true  do
 
       click_button 'Search'
 
-      expect(page).to have_current_path(namespace_project_commit_path(project.namespace, project, '6d394385cf567f80a8fd85055db1ab4c5295806f'))
+      expect(page).to have_current_path(project_commit_path(project, '6d394385cf567f80a8fd85055db1ab4c5295806f'))
     end
 
     it 'holds on /search page when the only commit is found by message' do
diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb
index f33406a40a781414596b3623e2de06afd7df7862..1000a0bdd89b449fd9f132965d15f19c4405afff 100644
--- a/spec/features/security/project/internal_access_spec.rb
+++ b/spec/features/security/project/internal_access_spec.rb
@@ -13,7 +13,7 @@ describe "Internal Project Access", feature: true  do
   end
 
   describe "GET /:project_path" do
-    subject { namespace_project_path(project.namespace, project) }
+    subject { project_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -27,7 +27,7 @@ describe "Internal Project Access", feature: true  do
   end
 
   describe "GET /:project_path/tree/master" do
-    subject { namespace_project_tree_path(project.namespace, project, project.repository.root_ref) }
+    subject { project_tree_path(project, project.repository.root_ref) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -41,7 +41,7 @@ describe "Internal Project Access", feature: true  do
   end
 
   describe "GET /:project_path/commits/master" do
-    subject { namespace_project_commits_path(project.namespace, project, project.repository.root_ref, limit: 1) }
+    subject { project_commits_path(project, project.repository.root_ref, limit: 1) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -55,7 +55,7 @@ describe "Internal Project Access", feature: true  do
   end
 
   describe "GET /:project_path/commit/:sha" do
-    subject { namespace_project_commit_path(project.namespace, project, project.repository.commit) }
+    subject { project_commit_path(project, project.repository.commit) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -69,7 +69,7 @@ describe "Internal Project Access", feature: true  do
   end
 
   describe "GET /:project_path/compare" do
-    subject { namespace_project_compare_index_path(project.namespace, project) }
+    subject { project_compare_index_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -83,7 +83,7 @@ describe "Internal Project Access", feature: true  do
   end
 
   describe "GET /:project_path/settings/members" do
-    subject { namespace_project_settings_members_path(project.namespace, project) }
+    subject { project_settings_members_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -97,7 +97,7 @@ describe "Internal Project Access", feature: true  do
   end
 
   describe "GET /:project_path/settings/ci_cd" do
-    subject { namespace_project_settings_ci_cd_path(project.namespace, project) }
+    subject { project_settings_ci_cd_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -111,7 +111,7 @@ describe "Internal Project Access", feature: true  do
   end
 
   describe "GET /:project_path/settings/repository" do
-    subject { namespace_project_settings_repository_path(project.namespace, project) }
+    subject { project_settings_repository_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -126,7 +126,7 @@ describe "Internal Project Access", feature: true  do
 
   describe "GET /:project_path/blob" do
     let(:commit) { project.repository.commit }
-    subject { namespace_project_blob_path(project.namespace, project, File.join(commit.id, '.gitignore')) }
+    subject { project_blob_path(project, File.join(commit.id, '.gitignore')) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -140,7 +140,7 @@ describe "Internal Project Access", feature: true  do
   end
 
   describe "GET /:project_path/edit" do
-    subject { edit_namespace_project_path(project.namespace, project) }
+    subject { edit_project_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -154,7 +154,7 @@ describe "Internal Project Access", feature: true  do
   end
 
   describe "GET /:project_path/deploy_keys" do
-    subject { namespace_project_deploy_keys_path(project.namespace, project) }
+    subject { project_deploy_keys_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -168,7 +168,7 @@ describe "Internal Project Access", feature: true  do
   end
 
   describe "GET /:project_path/issues" do
-    subject { namespace_project_issues_path(project.namespace, project) }
+    subject { project_issues_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -183,7 +183,7 @@ describe "Internal Project Access", feature: true  do
 
   describe "GET /:project_path/issues/:id/edit" do
     let(:issue) { create(:issue, project: project) }
-    subject { edit_namespace_project_issue_path(project.namespace, project, issue) }
+    subject { edit_project_issue_path(project, issue) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -197,7 +197,7 @@ describe "Internal Project Access", feature: true  do
   end
 
   describe "GET /:project_path/snippets" do
-    subject { namespace_project_snippets_path(project.namespace, project) }
+    subject { project_snippets_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -211,7 +211,7 @@ describe "Internal Project Access", feature: true  do
   end
 
   describe "GET /:project_path/snippets/new" do
-    subject { new_namespace_project_snippet_path(project.namespace, project) }
+    subject { new_project_snippet_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -225,7 +225,7 @@ describe "Internal Project Access", feature: true  do
   end
 
   describe "GET /:project_path/merge_requests" do
-    subject { namespace_project_merge_requests_path(project.namespace, project) }
+    subject { project_merge_requests_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -239,7 +239,7 @@ describe "Internal Project Access", feature: true  do
   end
 
   describe "GET /:project_path/merge_requests/new" do
-    subject { new_namespace_project_merge_request_path(project.namespace, project) }
+    subject { project_new_merge_request_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -253,7 +253,7 @@ describe "Internal Project Access", feature: true  do
   end
 
   describe "GET /:project_path/branches" do
-    subject { namespace_project_branches_path(project.namespace, project) }
+    subject { project_branches_path(project) }
 
     before do
       # Speed increase
@@ -272,7 +272,7 @@ describe "Internal Project Access", feature: true  do
   end
 
   describe "GET /:project_path/tags" do
-    subject { namespace_project_tags_path(project.namespace, project) }
+    subject { project_tags_path(project) }
 
     before do
       # Speed increase
@@ -291,7 +291,7 @@ describe "Internal Project Access", feature: true  do
   end
 
   describe "GET /:project_path/settings/integrations" do
-    subject { namespace_project_settings_integrations_path(project.namespace, project) }
+    subject { project_settings_integrations_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -305,7 +305,7 @@ describe "Internal Project Access", feature: true  do
   end
 
   describe "GET /:project_path/pipelines" do
-    subject { namespace_project_pipelines_path(project.namespace, project) }
+    subject { project_pipelines_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -320,7 +320,7 @@ describe "Internal Project Access", feature: true  do
 
   describe "GET /:project_path/pipelines/:id" do
     let(:pipeline) { create(:ci_pipeline, project: project) }
-    subject { namespace_project_pipeline_path(project.namespace, project, pipeline) }
+    subject { project_pipeline_path(project, pipeline) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -334,7 +334,7 @@ describe "Internal Project Access", feature: true  do
   end
 
   describe "GET /:project_path/builds" do
-    subject { namespace_project_jobs_path(project.namespace, project) }
+    subject { project_jobs_path(project) }
 
     context "when allowed for public and internal" do
       before do
@@ -372,7 +372,7 @@ describe "Internal Project Access", feature: true  do
   describe "GET /:project_path/builds/:id" do
     let(:pipeline) { create(:ci_pipeline, project: project) }
     let(:build) { create(:ci_build, pipeline: pipeline) }
-    subject { namespace_project_job_path(project.namespace, project, build.id) }
+    subject { project_job_path(project, build.id) }
 
     context "when allowed for public and internal" do
       before do
@@ -410,7 +410,7 @@ describe "Internal Project Access", feature: true  do
   describe 'GET /:project_path/builds/:id/trace' do
     let(:pipeline) { create(:ci_pipeline, project: project) }
     let(:build) { create(:ci_build, pipeline: pipeline) }
-    subject { trace_namespace_project_job_path(project.namespace, project, build.id) }
+    subject { trace_project_job_path(project, build.id) }
 
     context 'when allowed for public and internal' do
       before do
@@ -446,7 +446,7 @@ describe "Internal Project Access", feature: true  do
   end
 
   describe "GET /:project_path/pipeline_schedules" do
-    subject { namespace_project_pipeline_schedules_path(project.namespace, project) }
+    subject { project_pipeline_schedules_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -460,7 +460,7 @@ describe "Internal Project Access", feature: true  do
   end
 
   describe "GET /:project_path/environments" do
-    subject { namespace_project_environments_path(project.namespace, project) }
+    subject { project_environments_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -475,7 +475,7 @@ describe "Internal Project Access", feature: true  do
 
   describe "GET /:project_path/environments/:id" do
     let(:environment) { create(:environment, project: project) }
-    subject { namespace_project_environment_path(project.namespace, project, environment) }
+    subject { project_environment_path(project, environment) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -490,7 +490,7 @@ describe "Internal Project Access", feature: true  do
 
   describe "GET /:project_path/environments/:id/deployments" do
     let(:environment) { create(:environment, project: project) }
-    subject { namespace_project_environment_deployments_path(project.namespace, project, environment) }
+    subject { project_environment_deployments_path(project, environment) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -504,7 +504,7 @@ describe "Internal Project Access", feature: true  do
   end
 
   describe "GET /:project_path/environments/new" do
-    subject { new_namespace_project_environment_path(project.namespace, project) }
+    subject { new_project_environment_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -526,7 +526,7 @@ describe "Internal Project Access", feature: true  do
       project.container_repositories << container_repository
     end
 
-    subject { namespace_project_container_registry_index_path(project.namespace, project) }
+    subject { project_container_registry_index_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb
index b676c236758f161bf87d0d231c00764514c43443..94d759393caaa7649827a8e473d2ccd43e72c392 100644
--- a/spec/features/security/project/private_access_spec.rb
+++ b/spec/features/security/project/private_access_spec.rb
@@ -13,7 +13,7 @@ describe "Private Project Access", feature: true  do
   end
 
   describe "GET /:project_path" do
-    subject { namespace_project_path(project.namespace, project) }
+    subject { project_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -27,7 +27,7 @@ describe "Private Project Access", feature: true  do
   end
 
   describe "GET /:project_path/tree/master" do
-    subject { namespace_project_tree_path(project.namespace, project, project.repository.root_ref) }
+    subject { project_tree_path(project, project.repository.root_ref) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -41,7 +41,7 @@ describe "Private Project Access", feature: true  do
   end
 
   describe "GET /:project_path/commits/master" do
-    subject { namespace_project_commits_path(project.namespace, project, project.repository.root_ref, limit: 1) }
+    subject { project_commits_path(project, project.repository.root_ref, limit: 1) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -55,7 +55,7 @@ describe "Private Project Access", feature: true  do
   end
 
   describe "GET /:project_path/commit/:sha" do
-    subject { namespace_project_commit_path(project.namespace, project, project.repository.commit) }
+    subject { project_commit_path(project, project.repository.commit) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -69,7 +69,7 @@ describe "Private Project Access", feature: true  do
   end
 
   describe "GET /:project_path/compare" do
-    subject { namespace_project_compare_index_path(project.namespace, project) }
+    subject { project_compare_index_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -83,7 +83,7 @@ describe "Private Project Access", feature: true  do
   end
 
   describe "GET /:project_path/settings/members" do
-    subject { namespace_project_settings_members_path(project.namespace, project) }
+    subject { project_settings_members_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -97,7 +97,7 @@ describe "Private Project Access", feature: true  do
   end
 
   describe "GET /:project_path/settings/ci_cd" do
-    subject { namespace_project_settings_ci_cd_path(project.namespace, project) }
+    subject { project_settings_ci_cd_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -111,7 +111,7 @@ describe "Private Project Access", feature: true  do
   end
 
   describe "GET /:project_path/settings/repository" do
-    subject { namespace_project_settings_repository_path(project.namespace, project) }
+    subject { project_settings_repository_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -126,7 +126,7 @@ describe "Private Project Access", feature: true  do
 
   describe "GET /:project_path/blob" do
     let(:commit) { project.repository.commit }
-    subject { namespace_project_blob_path(project.namespace, project, File.join(commit.id, '.gitignore'))}
+    subject { project_blob_path(project, File.join(commit.id, '.gitignore'))}
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -140,7 +140,7 @@ describe "Private Project Access", feature: true  do
   end
 
   describe "GET /:project_path/edit" do
-    subject { edit_namespace_project_path(project.namespace, project) }
+    subject { edit_project_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -154,7 +154,7 @@ describe "Private Project Access", feature: true  do
   end
 
   describe "GET /:project_path/deploy_keys" do
-    subject { namespace_project_deploy_keys_path(project.namespace, project) }
+    subject { project_deploy_keys_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -168,7 +168,7 @@ describe "Private Project Access", feature: true  do
   end
 
   describe "GET /:project_path/issues" do
-    subject { namespace_project_issues_path(project.namespace, project) }
+    subject { project_issues_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -183,7 +183,7 @@ describe "Private Project Access", feature: true  do
 
   describe "GET /:project_path/issues/:id/edit" do
     let(:issue) { create(:issue, project: project) }
-    subject { edit_namespace_project_issue_path(project.namespace, project, issue) }
+    subject { edit_project_issue_path(project, issue) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -197,7 +197,7 @@ describe "Private Project Access", feature: true  do
   end
 
   describe "GET /:project_path/snippets" do
-    subject { namespace_project_snippets_path(project.namespace, project) }
+    subject { project_snippets_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -211,7 +211,7 @@ describe "Private Project Access", feature: true  do
   end
 
   describe "GET /:project_path/merge_requests" do
-    subject { namespace_project_merge_requests_path(project.namespace, project) }
+    subject { project_merge_requests_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -225,7 +225,7 @@ describe "Private Project Access", feature: true  do
   end
 
   describe "GET /:project_path/branches" do
-    subject { namespace_project_branches_path(project.namespace, project) }
+    subject { project_branches_path(project) }
 
     before do
       # Speed increase
@@ -244,7 +244,7 @@ describe "Private Project Access", feature: true  do
   end
 
   describe "GET /:project_path/tags" do
-    subject { namespace_project_tags_path(project.namespace, project) }
+    subject { project_tags_path(project) }
 
     before do
       # Speed increase
@@ -263,7 +263,7 @@ describe "Private Project Access", feature: true  do
   end
 
   describe "GET /:project_path/namespace/hooks" do
-    subject { namespace_project_settings_integrations_path(project.namespace, project) }
+    subject { project_settings_integrations_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -277,7 +277,7 @@ describe "Private Project Access", feature: true  do
   end
 
   describe "GET /:project_path/pipelines" do
-    subject { namespace_project_pipelines_path(project.namespace, project) }
+    subject { project_pipelines_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -304,7 +304,7 @@ describe "Private Project Access", feature: true  do
 
   describe "GET /:project_path/pipelines/:id" do
     let(:pipeline) { create(:ci_pipeline, project: project) }
-    subject { namespace_project_pipeline_path(project.namespace, project, pipeline) }
+    subject { project_pipeline_path(project, pipeline) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -330,7 +330,7 @@ describe "Private Project Access", feature: true  do
   end
 
   describe "GET /:project_path/builds" do
-    subject { namespace_project_jobs_path(project.namespace, project) }
+    subject { project_jobs_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -358,7 +358,7 @@ describe "Private Project Access", feature: true  do
   describe "GET /:project_path/builds/:id" do
     let(:pipeline) { create(:ci_pipeline, project: project) }
     let(:build) { create(:ci_build, pipeline: pipeline) }
-    subject { namespace_project_job_path(project.namespace, project, build.id) }
+    subject { project_job_path(project, build.id) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -391,7 +391,7 @@ describe "Private Project Access", feature: true  do
   describe 'GET /:project_path/builds/:id/trace' do
     let(:pipeline) { create(:ci_pipeline, project: project) }
     let(:build) { create(:ci_build, pipeline: pipeline) }
-    subject { trace_namespace_project_job_path(project.namespace, project, build.id) }
+    subject { trace_project_job_path(project, build.id) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -421,7 +421,7 @@ describe "Private Project Access", feature: true  do
   end
 
   describe "GET /:project_path/environments" do
-    subject { namespace_project_environments_path(project.namespace, project) }
+    subject { project_environments_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -436,7 +436,7 @@ describe "Private Project Access", feature: true  do
 
   describe "GET /:project_path/environments/:id" do
     let(:environment) { create(:environment, project: project) }
-    subject { namespace_project_environment_path(project.namespace, project, environment) }
+    subject { project_environment_path(project, environment) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -451,7 +451,7 @@ describe "Private Project Access", feature: true  do
 
   describe "GET /:project_path/environments/:id/deployments" do
     let(:environment) { create(:environment, project: project) }
-    subject { namespace_project_environment_deployments_path(project.namespace, project, environment) }
+    subject { project_environment_deployments_path(project, environment) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -465,7 +465,7 @@ describe "Private Project Access", feature: true  do
   end
 
   describe "GET /:project_path/environments/new" do
-    subject { new_namespace_project_environment_path(project.namespace, project) }
+    subject { new_project_environment_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -479,7 +479,7 @@ describe "Private Project Access", feature: true  do
   end
 
   describe "GET /:project_path/pipeline_schedules" do
-    subject { namespace_project_pipeline_schedules_path(project.namespace, project) }
+    subject { project_pipeline_schedules_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -493,7 +493,7 @@ describe "Private Project Access", feature: true  do
   end
 
   describe "GET /:project_path/pipeline_schedules/new" do
-    subject { new_namespace_project_pipeline_schedule_path(project.namespace, project) }
+    subject { new_project_pipeline_schedule_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -507,7 +507,7 @@ describe "Private Project Access", feature: true  do
   end
 
   describe "GET /:project_path/environments/new" do
-    subject { new_namespace_project_pipeline_schedule_path(project.namespace, project) }
+    subject { new_project_pipeline_schedule_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -529,7 +529,7 @@ describe "Private Project Access", feature: true  do
       project.container_repositories << container_repository
     end
 
-    subject { namespace_project_container_registry_index_path(project.namespace, project) }
+    subject { project_container_registry_index_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb
index 16a1331b2f32f60a9e9ed40481d1eb4009f064a6..d45e1dbc09b4c611bad771ee0c8ae45372bfef17 100644
--- a/spec/features/security/project/public_access_spec.rb
+++ b/spec/features/security/project/public_access_spec.rb
@@ -13,7 +13,7 @@ describe "Public Project Access", feature: true  do
   end
 
   describe "GET /:project_path" do
-    subject { namespace_project_path(project.namespace, project) }
+    subject { project_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -27,7 +27,7 @@ describe "Public Project Access", feature: true  do
   end
 
   describe "GET /:project_path/tree/master" do
-    subject { namespace_project_tree_path(project.namespace, project, project.repository.root_ref) }
+    subject { project_tree_path(project, project.repository.root_ref) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -41,7 +41,7 @@ describe "Public Project Access", feature: true  do
   end
 
   describe "GET /:project_path/commits/master" do
-    subject { namespace_project_commits_path(project.namespace, project, project.repository.root_ref, limit: 1) }
+    subject { project_commits_path(project, project.repository.root_ref, limit: 1) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -55,7 +55,7 @@ describe "Public Project Access", feature: true  do
   end
 
   describe "GET /:project_path/commit/:sha" do
-    subject { namespace_project_commit_path(project.namespace, project, project.repository.commit) }
+    subject { project_commit_path(project, project.repository.commit) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -69,7 +69,7 @@ describe "Public Project Access", feature: true  do
   end
 
   describe "GET /:project_path/compare" do
-    subject { namespace_project_compare_index_path(project.namespace, project) }
+    subject { project_compare_index_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -83,7 +83,7 @@ describe "Public Project Access", feature: true  do
   end
 
   describe "GET /:project_path/settings/members" do
-    subject { namespace_project_settings_members_path(project.namespace, project) }
+    subject { project_settings_members_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -97,7 +97,7 @@ describe "Public Project Access", feature: true  do
   end
 
   describe "GET /:project_path/settings/ci_cd" do
-    subject { namespace_project_settings_ci_cd_path(project.namespace, project) }
+    subject { project_settings_ci_cd_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -111,7 +111,7 @@ describe "Public Project Access", feature: true  do
   end
 
   describe "GET /:project_path/settings/repository" do
-    subject { namespace_project_settings_repository_path(project.namespace, project) }
+    subject { project_settings_repository_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -125,7 +125,7 @@ describe "Public Project Access", feature: true  do
   end
 
   describe "GET /:project_path/pipelines" do
-    subject { namespace_project_pipelines_path(project.namespace, project) }
+    subject { project_pipelines_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -140,7 +140,7 @@ describe "Public Project Access", feature: true  do
 
   describe "GET /:project_path/pipelines/:id" do
     let(:pipeline) { create(:ci_pipeline, project: project) }
-    subject { namespace_project_pipeline_path(project.namespace, project, pipeline) }
+    subject { project_pipeline_path(project, pipeline) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -154,7 +154,7 @@ describe "Public Project Access", feature: true  do
   end
 
   describe "GET /:project_path/builds" do
-    subject { namespace_project_jobs_path(project.namespace, project) }
+    subject { project_jobs_path(project) }
 
     context "when allowed for public" do
       before do
@@ -192,7 +192,7 @@ describe "Public Project Access", feature: true  do
   describe "GET /:project_path/builds/:id" do
     let(:pipeline) { create(:ci_pipeline, project: project) }
     let(:build) { create(:ci_build, pipeline: pipeline) }
-    subject { namespace_project_job_path(project.namespace, project, build.id) }
+    subject { project_job_path(project, build.id) }
 
     context "when allowed for public" do
       before do
@@ -230,7 +230,7 @@ describe "Public Project Access", feature: true  do
   describe 'GET /:project_path/builds/:id/trace' do
     let(:pipeline) { create(:ci_pipeline, project: project) }
     let(:build) { create(:ci_build, pipeline: pipeline) }
-    subject { trace_namespace_project_job_path(project.namespace, project, build.id) }
+    subject { trace_project_job_path(project, build.id) }
 
     context 'when allowed for public' do
       before do
@@ -266,7 +266,7 @@ describe "Public Project Access", feature: true  do
   end
 
   describe "GET /:project_path/pipeline_schedules" do
-    subject { namespace_project_pipeline_schedules_path(project.namespace, project) }
+    subject { project_pipeline_schedules_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -280,7 +280,7 @@ describe "Public Project Access", feature: true  do
   end
 
   describe "GET /:project_path/environments" do
-    subject { namespace_project_environments_path(project.namespace, project) }
+    subject { project_environments_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -295,7 +295,7 @@ describe "Public Project Access", feature: true  do
 
   describe "GET /:project_path/environments/:id" do
     let(:environment) { create(:environment, project: project) }
-    subject { namespace_project_environment_path(project.namespace, project, environment) }
+    subject { project_environment_path(project, environment) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -310,7 +310,7 @@ describe "Public Project Access", feature: true  do
 
   describe "GET /:project_path/environments/:id/deployments" do
     let(:environment) { create(:environment, project: project) }
-    subject { namespace_project_environment_deployments_path(project.namespace, project, environment) }
+    subject { project_environment_deployments_path(project, environment) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -324,7 +324,7 @@ describe "Public Project Access", feature: true  do
   end
 
   describe "GET /:project_path/environments/new" do
-    subject { new_namespace_project_environment_path(project.namespace, project) }
+    subject { new_project_environment_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -340,7 +340,7 @@ describe "Public Project Access", feature: true  do
   describe "GET /:project_path/blob" do
     let(:commit) { project.repository.commit }
 
-    subject { namespace_project_blob_path(project.namespace, project, File.join(commit.id, '.gitignore')) }
+    subject { project_blob_path(project, File.join(commit.id, '.gitignore')) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -353,7 +353,7 @@ describe "Public Project Access", feature: true  do
   end
 
   describe "GET /:project_path/edit" do
-    subject { edit_namespace_project_path(project.namespace, project) }
+    subject { edit_project_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -367,7 +367,7 @@ describe "Public Project Access", feature: true  do
   end
 
   describe "GET /:project_path/deploy_keys" do
-    subject { namespace_project_deploy_keys_path(project.namespace, project) }
+    subject { project_deploy_keys_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -381,7 +381,7 @@ describe "Public Project Access", feature: true  do
   end
 
   describe "GET /:project_path/issues" do
-    subject { namespace_project_issues_path(project.namespace, project) }
+    subject { project_issues_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -396,7 +396,7 @@ describe "Public Project Access", feature: true  do
 
   describe "GET /:project_path/issues/:id/edit" do
     let(:issue) { create(:issue, project: project) }
-    subject { edit_namespace_project_issue_path(project.namespace, project, issue) }
+    subject { edit_project_issue_path(project, issue) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -410,7 +410,7 @@ describe "Public Project Access", feature: true  do
   end
 
   describe "GET /:project_path/snippets" do
-    subject { namespace_project_snippets_path(project.namespace, project) }
+    subject { project_snippets_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -424,7 +424,7 @@ describe "Public Project Access", feature: true  do
   end
 
   describe "GET /:project_path/snippets/new" do
-    subject { new_namespace_project_snippet_path(project.namespace, project) }
+    subject { new_project_snippet_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -438,7 +438,7 @@ describe "Public Project Access", feature: true  do
   end
 
   describe "GET /:project_path/merge_requests" do
-    subject { namespace_project_merge_requests_path(project.namespace, project) }
+    subject { project_merge_requests_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -452,7 +452,7 @@ describe "Public Project Access", feature: true  do
   end
 
   describe "GET /:project_path/merge_requests/new" do
-    subject { new_namespace_project_merge_request_path(project.namespace, project) }
+    subject { project_new_merge_request_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -466,7 +466,7 @@ describe "Public Project Access", feature: true  do
   end
 
   describe "GET /:project_path/branches" do
-    subject { namespace_project_branches_path(project.namespace, project) }
+    subject { project_branches_path(project) }
 
     before do
       # Speed increase
@@ -485,7 +485,7 @@ describe "Public Project Access", feature: true  do
   end
 
   describe "GET /:project_path/tags" do
-    subject { namespace_project_tags_path(project.namespace, project) }
+    subject { project_tags_path(project) }
 
     before do
       # Speed increase
@@ -504,7 +504,7 @@ describe "Public Project Access", feature: true  do
   end
 
   describe "GET /:project_path/settings/integrations" do
-    subject { namespace_project_settings_integrations_path(project.namespace, project) }
+    subject { project_settings_integrations_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -526,7 +526,7 @@ describe "Public Project Access", feature: true  do
       project.container_repositories << container_repository
     end
 
-    subject { namespace_project_container_registry_index_path(project.namespace, project) }
+    subject { project_container_registry_index_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
diff --git a/spec/features/security/project/snippet/internal_access_spec.rb b/spec/features/security/project/snippet/internal_access_spec.rb
index 2659b3ee3ec3aee20c5ce0e5b5032dcd2fe83221..2420caa88c4efdab056def301d6a45546ee3b409 100644
--- a/spec/features/security/project/snippet/internal_access_spec.rb
+++ b/spec/features/security/project/snippet/internal_access_spec.rb
@@ -9,7 +9,7 @@ describe "Internal Project Snippets Access", feature: true  do
   let(:private_snippet)  { create(:project_snippet, :private,  project: project, author: project.owner) }
 
   describe "GET /:project_path/snippets" do
-    subject { namespace_project_snippets_path(project.namespace, project) }
+    subject { project_snippets_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -23,7 +23,7 @@ describe "Internal Project Snippets Access", feature: true  do
   end
 
   describe "GET /:project_path/snippets/new" do
-    subject { new_namespace_project_snippet_path(project.namespace, project) }
+    subject { new_project_snippet_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -38,7 +38,7 @@ describe "Internal Project Snippets Access", feature: true  do
 
   describe "GET /:project_path/snippets/:id" do
     context "for an internal snippet" do
-      subject { namespace_project_snippet_path(project.namespace, project, internal_snippet) }
+      subject { project_snippet_path(project, internal_snippet) }
 
       it { is_expected.to be_allowed_for(:admin) }
       it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -52,7 +52,7 @@ describe "Internal Project Snippets Access", feature: true  do
     end
 
     context "for a private snippet" do
-      subject { namespace_project_snippet_path(project.namespace, project, private_snippet) }
+      subject { project_snippet_path(project, private_snippet) }
 
       it { is_expected.to be_allowed_for(:admin) }
       it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -68,7 +68,7 @@ describe "Internal Project Snippets Access", feature: true  do
 
   describe "GET /:project_path/snippets/:id/raw" do
     context "for an internal snippet" do
-      subject { raw_namespace_project_snippet_path(project.namespace, project, internal_snippet) }
+      subject { raw_project_snippet_path(project, internal_snippet) }
 
       it { is_expected.to be_allowed_for(:admin) }
       it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -82,7 +82,7 @@ describe "Internal Project Snippets Access", feature: true  do
     end
 
     context "for a private snippet" do
-      subject { raw_namespace_project_snippet_path(project.namespace, project, private_snippet) }
+      subject { raw_project_snippet_path(project, private_snippet) }
 
       it { is_expected.to be_allowed_for(:admin) }
       it { is_expected.to be_allowed_for(:owner).of(project) }
diff --git a/spec/features/security/project/snippet/private_access_spec.rb b/spec/features/security/project/snippet/private_access_spec.rb
index 6eb9f163bd5b25d782c79a3c13f20c2f14ebfaab..0b8548a675b8e23134f3372e2227104fe66de23e 100644
--- a/spec/features/security/project/snippet/private_access_spec.rb
+++ b/spec/features/security/project/snippet/private_access_spec.rb
@@ -8,7 +8,7 @@ describe "Private Project Snippets Access", feature: true  do
   let(:private_snippet)  { create(:project_snippet, :private, project: project, author: project.owner) }
 
   describe "GET /:project_path/snippets" do
-    subject { namespace_project_snippets_path(project.namespace, project) }
+    subject { project_snippets_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -22,7 +22,7 @@ describe "Private Project Snippets Access", feature: true  do
   end
 
   describe "GET /:project_path/snippets/new" do
-    subject { new_namespace_project_snippet_path(project.namespace, project) }
+    subject { new_project_snippet_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -36,7 +36,7 @@ describe "Private Project Snippets Access", feature: true  do
   end
 
   describe "GET /:project_path/snippets/:id for a private snippet" do
-    subject { namespace_project_snippet_path(project.namespace, project, private_snippet) }
+    subject { project_snippet_path(project, private_snippet) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -50,7 +50,7 @@ describe "Private Project Snippets Access", feature: true  do
   end
 
   describe "GET /:project_path/snippets/:id/raw for a private snippet" do
-    subject { raw_namespace_project_snippet_path(project.namespace, project, private_snippet) }
+    subject { raw_project_snippet_path(project, private_snippet) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
diff --git a/spec/features/security/project/snippet/public_access_spec.rb b/spec/features/security/project/snippet/public_access_spec.rb
index f3329d0bc9609bb83f0e6f4c8769a584a2ed5ca2..153f8f964a6597576cca523a795f4f437a02538d 100644
--- a/spec/features/security/project/snippet/public_access_spec.rb
+++ b/spec/features/security/project/snippet/public_access_spec.rb
@@ -10,7 +10,7 @@ describe "Public Project Snippets Access", feature: true  do
   let(:private_snippet)  { create(:project_snippet, :private,  project: project, author: project.owner) }
 
   describe "GET /:project_path/snippets" do
-    subject { namespace_project_snippets_path(project.namespace, project) }
+    subject { project_snippets_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -24,7 +24,7 @@ describe "Public Project Snippets Access", feature: true  do
   end
 
   describe "GET /:project_path/snippets/new" do
-    subject { new_namespace_project_snippet_path(project.namespace, project) }
+    subject { new_project_snippet_path(project) }
 
     it { is_expected.to be_allowed_for(:admin) }
     it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -39,7 +39,7 @@ describe "Public Project Snippets Access", feature: true  do
 
   describe "GET /:project_path/snippets/:id" do
     context "for a public snippet" do
-      subject { namespace_project_snippet_path(project.namespace, project, public_snippet) }
+      subject { project_snippet_path(project, public_snippet) }
 
       it { is_expected.to be_allowed_for(:admin) }
       it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -53,7 +53,7 @@ describe "Public Project Snippets Access", feature: true  do
     end
 
     context "for an internal snippet" do
-      subject { namespace_project_snippet_path(project.namespace, project, internal_snippet) }
+      subject { project_snippet_path(project, internal_snippet) }
 
       it { is_expected.to be_allowed_for(:admin) }
       it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -67,7 +67,7 @@ describe "Public Project Snippets Access", feature: true  do
     end
 
     context "for a private snippet" do
-      subject { namespace_project_snippet_path(project.namespace, project, private_snippet) }
+      subject { project_snippet_path(project, private_snippet) }
 
       it { is_expected.to be_allowed_for(:admin) }
       it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -83,7 +83,7 @@ describe "Public Project Snippets Access", feature: true  do
 
   describe "GET /:project_path/snippets/:id/raw" do
     context "for a public snippet" do
-      subject { raw_namespace_project_snippet_path(project.namespace, project, public_snippet) }
+      subject { raw_project_snippet_path(project, public_snippet) }
 
       it { is_expected.to be_allowed_for(:admin) }
       it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -97,7 +97,7 @@ describe "Public Project Snippets Access", feature: true  do
     end
 
     context "for an internal snippet" do
-      subject { raw_namespace_project_snippet_path(project.namespace, project, internal_snippet) }
+      subject { raw_project_snippet_path(project, internal_snippet) }
 
       it { is_expected.to be_allowed_for(:admin) }
       it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -111,7 +111,7 @@ describe "Public Project Snippets Access", feature: true  do
     end
 
     context "for a private snippet" do
-      subject { raw_namespace_project_snippet_path(project.namespace, project, private_snippet) }
+      subject { raw_project_snippet_path(project, private_snippet) }
 
       it { is_expected.to be_allowed_for(:admin) }
       it { is_expected.to be_allowed_for(:owner).of(project) }
diff --git a/spec/features/snippets/explore_spec.rb b/spec/features/snippets/explore_spec.rb
index fd097fe2e7484bc292f976c56df47f69fee12650..ec75817b942cfc1bce43763b46c02543bb0df7e7 100644
--- a/spec/features/snippets/explore_spec.rb
+++ b/spec/features/snippets/explore_spec.rb
@@ -6,7 +6,7 @@ feature 'Explore Snippets', feature: true do
   let!(:private_snippet) { create(:personal_snippet, :private) }
 
   scenario 'User should see snippets that are not private' do
-    login_as create(:user)
+    gitlab_sign_in create(:user)
     visit explore_snippets_path
 
     expect(page).to have_content(public_snippet.title)
@@ -15,7 +15,7 @@ feature 'Explore Snippets', feature: true do
   end
 
   scenario 'External user should see only public snippets' do
-    login_as create(:user, :external)
+    gitlab_sign_in create(:user, :external)
     visit explore_snippets_path
 
     expect(page).to have_content(public_snippet.title)
diff --git a/spec/features/snippets/internal_snippet_spec.rb b/spec/features/snippets/internal_snippet_spec.rb
index 93382f4c3591bd9d3539fd45e782bf05191480e7..3babb1c02ccc29774cb307f674ed1ab290da3523 100644
--- a/spec/features/snippets/internal_snippet_spec.rb
+++ b/spec/features/snippets/internal_snippet_spec.rb
@@ -5,7 +5,7 @@ feature 'Internal Snippets', feature: true, js: true do
 
   describe 'normal user' do
     before do
-      login_as :user
+      gitlab_sign_in :user
     end
 
     scenario 'sees internal snippets' do
diff --git a/spec/features/snippets/notes_on_personal_snippets_spec.rb b/spec/features/snippets/notes_on_personal_snippets_spec.rb
index 44b0c89fac720f52cf71dfddeeb38ee38a452f92..c7e2e3d8a3446dc466f6250aff7271e1c7c5c428 100644
--- a/spec/features/snippets/notes_on_personal_snippets_spec.rb
+++ b/spec/features/snippets/notes_on_personal_snippets_spec.rb
@@ -14,7 +14,7 @@ describe 'Comments on personal snippets', :js, feature: true do
   let!(:other_note) { create(:note_on_personal_snippet) }
 
   before do
-    login_as user
+    gitlab_sign_in user
     visit snippet_path(snippet)
   end
 
@@ -33,6 +33,7 @@ describe 'Comments on personal snippets', :js, feature: true do
         expect(page).to have_selector('.note-emoji-button')
       end
 
+      find('body').click # close dropdown
       open_more_actions_dropdown(snippet_notes[1])
 
       page.within("#notes-list li#note_#{snippet_notes[1].id}") do
@@ -46,8 +47,8 @@ describe 'Comments on personal snippets', :js, feature: true do
   context 'when submitting a note' do
     it 'shows a valid form' do
       is_expected.to have_css('.js-main-target-form', visible: true, count: 1)
-      expect(find('.js-main-target-form .js-comment-button').value).
-        to eq('Comment')
+      expect(find('.js-main-target-form .js-comment-button').value)
+        .to eq('Comment')
 
       page.within('.js-main-target-form') do
         expect(page).not_to have_link('Cancel')
@@ -70,6 +71,22 @@ describe 'Comments on personal snippets', :js, feature: true do
 
       expect(find('div#notes')).to have_content('This is awesome!')
     end
+
+    it 'should not have autocomplete' do
+      wait_for_requests
+      request_count_before = page.driver.network_traffic.count
+
+      find('#note_note').native.send_keys('')
+      fill_in 'note[note]', with: '@'
+
+      wait_for_requests
+      request_count_after = page.driver.network_traffic.count
+
+      # This selector probably won't be in place even if autocomplete was enabled
+      # but we want to make sure
+      expect(page).not_to have_selector('.atwho-view')
+      expect(request_count_before).to eq(request_count_after)
+    end
   end
 
   context 'when editing a note' do
diff --git a/spec/features/snippets/search_snippets_spec.rb b/spec/features/snippets/search_snippets_spec.rb
index 146cd3af848741eccffd8b9073d976736af557be..4c21e7321f4e16690aea2f58536805288867c407 100644
--- a/spec/features/snippets/search_snippets_spec.rb
+++ b/spec/features/snippets/search_snippets_spec.rb
@@ -5,7 +5,7 @@ feature 'Search Snippets', feature: true do
     public_snippet = create(:personal_snippet, :public, title: 'Beginning and Middle')
     private_snippet = create(:personal_snippet, :private, title: 'Middle and End')
 
-    login_as private_snippet.author
+    gitlab_sign_in private_snippet.author
     visit dashboard_snippets_path
 
     page.within '.search' do
@@ -41,7 +41,7 @@ feature 'Search Snippets', feature: true do
            CONTENT
           )
 
-    login_as create(:user)
+    gitlab_sign_in create(:user)
     visit dashboard_snippets_path
 
     page.within '.search' do
diff --git a/spec/features/snippets/create_snippet_spec.rb b/spec/features/snippets/user_creates_snippet_spec.rb
similarity index 96%
rename from spec/features/snippets/create_snippet_spec.rb
rename to spec/features/snippets/user_creates_snippet_spec.rb
index ddd31ede064de8323c6a899f4a6cfa7b4b31a99c..57dec14b480db71150ced904be5965ba0c9e89cf 100644
--- a/spec/features/snippets/create_snippet_spec.rb
+++ b/spec/features/snippets/user_creates_snippet_spec.rb
@@ -1,10 +1,12 @@
 require 'rails_helper'
 
-feature 'Create Snippet', :js, feature: true do
+feature 'User creates snippet', :js, feature: true do
   include DropzoneHelper
 
+  let(:user) { create(:user) }
+
   before do
-    login_as :user
+    sign_in(user)
     visit new_snippet_path
   end
 
diff --git a/spec/features/snippets/user_deletes_snippet_spec.rb b/spec/features/snippets/user_deletes_snippet_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..162c2c9e730daa6ba42fc3e0f8921f77ac368920
--- /dev/null
+++ b/spec/features/snippets/user_deletes_snippet_spec.rb
@@ -0,0 +1,19 @@
+require 'rails_helper'
+
+feature 'User deletes snippet', feature: true do
+  let(:user) { create(:user) }
+  let(:content) { 'puts "test"' }
+  let(:snippet) { create(:personal_snippet, :public, content: content, author: user) }
+
+  before do
+    sign_in(user)
+
+    visit snippet_path(snippet)
+  end
+
+  it 'deletes the snippet' do
+    first(:link, 'Delete').click
+
+    expect(page).not_to have_content(snippet.title)
+  end
+end
diff --git a/spec/features/snippets/edit_snippet_spec.rb b/spec/features/snippets/user_edits_snippet_spec.rb
similarity index 59%
rename from spec/features/snippets/edit_snippet_spec.rb
rename to spec/features/snippets/user_edits_snippet_spec.rb
index 89ae593db88454b754707ddd1ae6c29c2bae3b47..cff6442387398f01680cab5406b566d3de54607f 100644
--- a/spec/features/snippets/edit_snippet_spec.rb
+++ b/spec/features/snippets/user_edits_snippet_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-feature 'Edit Snippet', :js, feature: true do
+feature 'User edits snippet', :js, feature: true do
   include DropzoneHelper
 
   let(:file_name) { 'test.rb' }
@@ -10,7 +10,7 @@ feature 'Edit Snippet', :js, feature: true do
   let(:snippet) { create(:personal_snippet, :public, file_name: file_name, content: content, author: user) }
 
   before do
-    login_as(user)
+    sign_in(user)
 
     visit edit_snippet_path(snippet)
     wait_for_requests
@@ -27,7 +27,7 @@ feature 'Edit Snippet', :js, feature: true do
 
   it 'updates the snippet with files attached' do
     dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif')
-    expect(page.find_field("personal_snippet_description").value).to have_content('banana_sample')
+    expect(page.find_field('personal_snippet_description').value).to have_content('banana_sample')
 
     click_button('Save changes')
     wait_for_requests
@@ -35,4 +35,24 @@ feature 'Edit Snippet', :js, feature: true do
     link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
     expect(link).to match(%r{/uploads/personal_snippet/#{snippet.id}/\h{32}/banana_sample\.gif\z})
   end
+
+  it 'updates the snippet to make it internal' do
+    choose 'Internal'
+
+    click_button 'Save changes'
+    wait_for_requests
+
+    expect(page).to have_no_xpath("//i[@class='fa fa-lock']")
+    expect(page).to have_xpath("//i[@class='fa fa-shield']")
+  end
+
+  it 'updates the snippet to make it public' do
+    choose 'Public'
+
+    click_button 'Save changes'
+    wait_for_requests
+
+    expect(page).to have_no_xpath("//i[@class='fa fa-lock']")
+    expect(page).to have_xpath("//i[@class='fa fa-globe']")
+  end
 end
diff --git a/spec/features/snippets/user_snippets_spec.rb b/spec/features/snippets/user_snippets_spec.rb
index 191c2fb9a22eb91995faad831c22552484a6aa16..b971c6aab530e5b1481ff8deffedd183722512b8 100644
--- a/spec/features/snippets/user_snippets_spec.rb
+++ b/spec/features/snippets/user_snippets_spec.rb
@@ -7,7 +7,7 @@ feature 'User Snippets', feature: true do
   let!(:private_snippet) { create(:personal_snippet, :private, author: author, title: "This is a private snippet") }
 
   background do
-    login_as author
+    gitlab_sign_in author
     visit dashboard_snippets_path
   end
 
diff --git a/spec/features/tags/master_creates_tag_spec.rb b/spec/features/tags/master_creates_tag_spec.rb
index af25eebed1395b62f0ad1fbd84a2b40dcd818090..3bf5544a837e9887ed21beeaca93f1c29c8dec45 100644
--- a/spec/features/tags/master_creates_tag_spec.rb
+++ b/spec/features/tags/master_creates_tag_spec.rb
@@ -6,62 +6,80 @@ feature 'Master creates tag', feature: true do
 
   before do
     project.team << [user, :master]
-    login_with(user)
-    visit namespace_project_tags_path(project.namespace, project)
+    gitlab_sign_in(user)
   end
 
-  scenario 'with an invalid name displays an error' do
-    create_tag_in_form(tag: 'v 1.0', ref: 'master')
+  context 'from tag list' do
+    before do
+      visit project_tags_path(project)
+    end
 
-    expect(page).to have_content 'Tag name invalid'
-  end
+    scenario 'with an invalid name displays an error' do
+      create_tag_in_form(tag: 'v 1.0', ref: 'master')
 
-  scenario 'with an invalid reference displays an error' do
-    create_tag_in_form(tag: 'v2.0', ref: 'foo')
+      expect(page).to have_content 'Tag name invalid'
+    end
 
-    expect(page).to have_content 'Target foo is invalid'
-  end
+    scenario 'with an invalid reference displays an error' do
+      create_tag_in_form(tag: 'v2.0', ref: 'foo')
 
-  scenario 'that already exists displays an error' do
-    create_tag_in_form(tag: 'v1.1.0', ref: 'master')
+      expect(page).to have_content 'Target foo is invalid'
+    end
 
-    expect(page).to have_content 'Tag v1.1.0 already exists'
-  end
+    scenario 'that already exists displays an error' do
+      create_tag_in_form(tag: 'v1.1.0', ref: 'master')
+
+      expect(page).to have_content 'Tag v1.1.0 already exists'
+    end
 
-  scenario 'with multiline message displays the message in a <pre> block' do
-    create_tag_in_form(tag: 'v3.0', ref: 'master', message: "Awesome tag message\n\n- hello\n- world")
+    scenario 'with multiline message displays the message in a <pre> block' do
+      create_tag_in_form(tag: 'v3.0', ref: 'master', message: "Awesome tag message\n\n- hello\n- world")
 
-    expect(current_path).to eq(
-      namespace_project_tag_path(project.namespace, project, 'v3.0'))
-    expect(page).to have_content 'v3.0'
-    page.within 'pre.wrap' do
-      expect(page).to have_content "Awesome tag message\n\n- hello\n- world"
+      expect(current_path).to eq(
+        project_tag_path(project, 'v3.0'))
+      expect(page).to have_content 'v3.0'
+      page.within 'pre.wrap' do
+        expect(page).to have_content "Awesome tag message\n\n- hello\n- world"
+      end
     end
-  end
 
-  scenario 'with multiline release notes parses the release note as Markdown' do
-    create_tag_in_form(tag: 'v4.0', ref: 'master', desc: "Awesome release notes\n\n- hello\n- world")
+    scenario 'with multiline release notes parses the release note as Markdown' do
+      create_tag_in_form(tag: 'v4.0', ref: 'master', desc: "Awesome release notes\n\n- hello\n- world")
 
-    expect(current_path).to eq(
-      namespace_project_tag_path(project.namespace, project, 'v4.0'))
-    expect(page).to have_content 'v4.0'
-    page.within '.description' do
-      expect(page).to have_content 'Awesome release notes'
-      expect(page).to have_selector('ul li', count: 2)
+      expect(current_path).to eq(
+        project_tag_path(project, 'v4.0'))
+      expect(page).to have_content 'v4.0'
+      page.within '.description' do
+        expect(page).to have_content 'Awesome release notes'
+        expect(page).to have_selector('ul li', count: 2)
+      end
+    end
+
+    scenario 'opens dropdown for ref', js: true do
+      click_link 'New tag'
+      ref_row = find('.form-group:nth-of-type(2) .col-sm-10')
+      page.within ref_row do
+        ref_input = find('[name="ref"]', visible: false)
+        expect(ref_input.value).to eq 'master'
+        expect(find('.dropdown-toggle-text')).to have_content 'master'
+
+        find('.js-branch-select').trigger('click')
+
+        expect(find('.dropdown-menu')).to have_content 'empty-branch'
+      end
     end
   end
 
-  scenario 'opens dropdown for ref', js: true do
-    click_link 'New tag'
-    ref_row = find('.form-group:nth-of-type(2) .col-sm-10')
-    page.within ref_row do
-      ref_input = find('[name="ref"]', visible: false)
-      expect(ref_input.value).to eq 'master'
-      expect(find('.dropdown-toggle-text')).to have_content 'master'
+  context 'from new tag page' do
+    before do
+      visit new_project_tag_path(project)
+    end
 
-      find('.js-branch-select').trigger('click')
+    it 'description has autocomplete', :js do
+      find('#release_description').native.send_keys('')
+      fill_in 'release_description', with: '@'
 
-      expect(find('.dropdown-menu')).to have_content 'empty-branch'
+      expect(page).to have_selector('.atwho-view')
     end
   end
 
diff --git a/spec/features/tags/master_deletes_tag_spec.rb b/spec/features/tags/master_deletes_tag_spec.rb
index ccfafe6db7d5d3427adad86d079ab7bd91b4f89f..04f9cecd46d1b519ea3413210d88d3b52911ae1c 100644
--- a/spec/features/tags/master_deletes_tag_spec.rb
+++ b/spec/features/tags/master_deletes_tag_spec.rb
@@ -6,8 +6,8 @@ feature 'Master deletes tag', feature: true do
 
   before do
     project.team << [user, :master]
-    login_with(user)
-    visit namespace_project_tags_path(project.namespace, project)
+    gitlab_sign_in(user)
+    visit project_tags_path(project)
   end
 
   context 'from the tags list page', js: true do
@@ -24,12 +24,12 @@ feature 'Master deletes tag', feature: true do
     scenario 'deletes the tag' do
       click_on 'v1.0.0'
       expect(current_path).to eq(
-        namespace_project_tag_path(project.namespace, project, 'v1.0.0'))
+        project_tag_path(project, 'v1.0.0'))
 
       click_on 'Delete tag'
 
       expect(current_path).to eq(
-        namespace_project_tags_path(project.namespace, project))
+        project_tags_path(project))
       expect(page).not_to have_content 'v1.0.0'
     end
   end
diff --git a/spec/features/tags/master_updates_tag_spec.rb b/spec/features/tags/master_updates_tag_spec.rb
index 6b5b3122f725279c68ea2c779cbe0bf37914eecf..092ffbb6d23f74bae7d3e75dc6fcc6c22409287d 100644
--- a/spec/features/tags/master_updates_tag_spec.rb
+++ b/spec/features/tags/master_updates_tag_spec.rb
@@ -6,8 +6,8 @@ feature 'Master updates tag', feature: true do
 
   before do
     project.team << [user, :master]
-    login_with(user)
-    visit namespace_project_tags_path(project.namespace, project)
+    gitlab_sign_in(user)
+    visit project_tags_path(project)
   end
 
   context 'from the tags list page' do
@@ -20,10 +20,21 @@ feature 'Master updates tag', feature: true do
       click_button 'Save changes'
 
       expect(current_path).to eq(
-        namespace_project_tag_path(project.namespace, project, 'v1.1.0'))
+        project_tag_path(project, 'v1.1.0'))
       expect(page).to have_content 'v1.1.0'
       expect(page).to have_content 'Awesome release notes'
     end
+
+    scenario 'description has autocomplete', :js do
+      page.within(first('.content-list .controls')) do
+        click_link 'Edit release notes'
+      end
+
+      find('#release_description').native.send_keys('')
+      fill_in 'release_description', with: '@'
+
+      expect(page).to have_selector('.atwho-view')
+    end
   end
 
   context 'from a specific tag page' do
@@ -34,7 +45,7 @@ feature 'Master updates tag', feature: true do
       click_button 'Save changes'
 
       expect(current_path).to eq(
-        namespace_project_tag_path(project.namespace, project, 'v1.1.0'))
+        project_tag_path(project, 'v1.1.0'))
       expect(page).to have_content 'v1.1.0'
       expect(page).to have_content 'Awesome release notes'
     end
diff --git a/spec/features/tags/master_views_tags_spec.rb b/spec/features/tags/master_views_tags_spec.rb
index 922ac15a2ebcd2974a23de7e68bac3484f83ec39..b1f3207eeea7e1564f60603a986f0f0262465c8a 100644
--- a/spec/features/tags/master_views_tags_spec.rb
+++ b/spec/features/tags/master_views_tags_spec.rb
@@ -5,19 +5,19 @@ feature 'Master views tags', feature: true do
 
   before do
     project.team << [user, :master]
-    login_with(user)
+    gitlab_sign_in(user)
   end
 
   context 'when project has no tags' do
     let(:project) { create(:project_empty_repo) }
     before do
-      visit namespace_project_path(project.namespace, project)
+      visit project_path(project)
       click_on 'README'
       fill_in :commit_message, with: 'Add a README file', visible: true
       # Remove pre-receive hook so we can push without auth
       FileUtils.rm_f(File.join(project.repository.path, 'hooks', 'pre-receive'))
       click_button 'Commit changes'
-      visit namespace_project_tags_path(project.namespace, project)
+      visit project_tags_path(project)
     end
 
     scenario 'displays a specific message' do
@@ -30,15 +30,15 @@ feature 'Master views tags', feature: true do
     let(:repository) { project.repository }
 
     before do
-      visit namespace_project_tags_path(project.namespace, project)
+      visit project_tags_path(project)
     end
 
     scenario 'avoids a N+1 query in branches index' do
-      control_count = ActiveRecord::QueryRecorder.new { visit namespace_project_tags_path(project.namespace, project) }.count
+      control_count = ActiveRecord::QueryRecorder.new { visit project_tags_path(project) }.count
 
       %w(one two three four five).each { |tag| repository.add_tag(user, tag, 'master', 'foo') }
 
-      expect { visit namespace_project_tags_path(project.namespace, project) }.not_to exceed_query_limit(control_count)
+      expect { visit project_tags_path(project) }.not_to exceed_query_limit(control_count)
     end
 
     scenario 'views the tags list page' do
@@ -49,7 +49,7 @@ feature 'Master views tags', feature: true do
       click_on 'v1.0.0'
 
       expect(current_path).to eq(
-        namespace_project_tag_path(project.namespace, project, 'v1.0.0'))
+        project_tag_path(project, 'v1.0.0'))
       expect(page).to have_content 'v1.0.0'
       expect(page).to have_content 'This tag has no release notes.'
     end
@@ -59,24 +59,24 @@ feature 'Master views tags', feature: true do
         click_on 'v1.0.0'
 
         expect(current_path).to eq(
-          namespace_project_tag_path(project.namespace, project, 'v1.0.0'))
+          project_tag_path(project, 'v1.0.0'))
 
         click_on 'Browse files'
 
         expect(current_path).to eq(
-          namespace_project_tree_path(project.namespace, project, 'v1.0.0'))
+          project_tree_path(project, 'v1.0.0'))
       end
 
       scenario 'has a button to browse commits' do
         click_on 'v1.0.0'
 
         expect(current_path).to eq(
-          namespace_project_tag_path(project.namespace, project, 'v1.0.0'))
+          project_tag_path(project, 'v1.0.0'))
 
         click_on 'Browse commits'
 
         expect(current_path).to eq(
-          namespace_project_commits_path(project.namespace, project, 'v1.0.0'))
+          project_commits_path(project, 'v1.0.0'))
       end
     end
   end
diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb
index 51b1b8e2328ac4e06689e1f9ee5e5969ab465cb5..dfc362321aaaa0f5748460fa8eef213ca5a4c674 100644
--- a/spec/features/task_lists_spec.rb
+++ b/spec/features/task_lists_spec.rb
@@ -59,7 +59,7 @@ feature 'Task Lists', feature: true do
   end
 
   def visit_issue(project, issue)
-    visit namespace_project_issue_path(project.namespace, project, issue)
+    visit project_issue_path(project, issue)
   end
 
   describe 'for Issues', feature: true do
@@ -98,7 +98,7 @@ feature 'Task Lists', feature: true do
       end
 
       it 'provides a summary on Issues#index' do
-        visit namespace_project_issues_path(project.namespace, project)
+        visit project_issues_path(project)
         expect(page).to have_content("2 of 6 tasks completed")
       end
     end
@@ -116,7 +116,7 @@ feature 'Task Lists', feature: true do
       end
 
       it 'provides a summary on Issues#index' do
-        visit namespace_project_issues_path(project.namespace, project)
+        visit project_issues_path(project)
 
         expect(page).to have_content("0 of 1 task completed")
       end
@@ -135,7 +135,7 @@ feature 'Task Lists', feature: true do
       end
 
       it 'provides a summary on Issues#index' do
-        visit namespace_project_issues_path(project.namespace, project)
+        visit project_issues_path(project)
 
         expect(page).to have_content("1 of 1 task completed")
       end
@@ -242,7 +242,7 @@ feature 'Task Lists', feature: true do
 
   describe 'for Merge Requests' do
     def visit_merge_request(project, merge)
-      visit namespace_project_merge_request_path(project.namespace, project, merge)
+      visit project_merge_request_path(project, merge)
     end
 
     describe 'multiple tasks' do
@@ -281,7 +281,7 @@ feature 'Task Lists', feature: true do
       end
 
       it 'provides a summary on MergeRequests#index' do
-        visit namespace_project_merge_requests_path(project.namespace, project)
+        visit project_merge_requests_path(project)
         expect(page).to have_content("2 of 6 tasks completed")
       end
     end
@@ -298,7 +298,7 @@ feature 'Task Lists', feature: true do
       end
 
       it 'provides a summary on MergeRequests#index' do
-        visit namespace_project_merge_requests_path(project.namespace, project)
+        visit project_merge_requests_path(project)
         expect(page).to have_content("0 of 1 task completed")
       end
     end
@@ -315,7 +315,7 @@ feature 'Task Lists', feature: true do
       end
 
       it 'provides a summary on MergeRequests#index' do
-        visit namespace_project_merge_requests_path(project.namespace, project)
+        visit project_merge_requests_path(project)
         expect(page).to have_content("1 of 1 task completed")
       end
     end
diff --git a/spec/features/todos/todos_spec.rb b/spec/features/todos/todos_spec.rb
deleted file mode 100644
index feb2fe8a7d1af25568c337397748248b67dec317..0000000000000000000000000000000000000000
--- a/spec/features/todos/todos_spec.rb
+++ /dev/null
@@ -1,355 +0,0 @@
-require 'spec_helper'
-
-describe 'Dashboard Todos', feature: true do
-  let(:user)    { create(:user) }
-  let(:author)  { create(:user) }
-  let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
-  let(:issue)   { create(:issue, due_date: Date.today) }
-
-  describe 'GET /dashboard/todos' do
-    context 'User does not have todos' do
-      before do
-        login_as(user)
-        visit dashboard_todos_path
-      end
-      it 'shows "All done" message' do
-        expect(page).to have_content "Todos let you see what you should do next."
-      end
-    end
-
-    context 'User has a todo', js: true do
-      before do
-        create(:todo, :mentioned, user: user, project: project, target: issue, author: author)
-        login_as(user)
-        visit dashboard_todos_path
-      end
-
-      it 'has todo present' do
-        expect(page).to have_selector('.todos-list .todo', count: 1)
-      end
-
-      it 'shows due date as today' do
-        within first('.todo') do
-          expect(page).to have_content 'Due today'
-        end
-      end
-
-      shared_examples 'deleting the todo' do
-        before do
-          within first('.todo') do
-            click_link 'Done'
-          end
-        end
-
-        it 'is marked as done-reversible in the list' do
-          expect(page).to have_selector('.todos-list .todo.todo-pending.done-reversible')
-        end
-
-        it 'shows Undo button' do
-          expect(page).to have_selector('.js-undo-todo', visible: true)
-          expect(page).to have_selector('.js-done-todo', visible: false)
-        end
-
-        it 'updates todo count' do
-          expect(page).to have_content 'To do 0'
-          expect(page).to have_content 'Done 1'
-        end
-
-        it 'has not "All done" message' do
-          expect(page).not_to have_selector('.todos-all-done')
-        end
-      end
-
-      shared_examples 'deleting and restoring the todo' do
-        before do
-          within first('.todo') do
-            click_link 'Done'
-            wait_for_requests
-            click_link 'Undo'
-          end
-        end
-
-        it 'is marked back as pending in the list' do
-          expect(page).not_to have_selector('.todos-list .todo.todo-pending.done-reversible')
-          expect(page).to have_selector('.todos-list .todo.todo-pending')
-        end
-
-        it 'shows Done button' do
-          expect(page).to have_selector('.js-undo-todo', visible: false)
-          expect(page).to have_selector('.js-done-todo', visible: true)
-        end
-
-        it 'updates todo count' do
-          expect(page).to have_content 'To do 1'
-          expect(page).to have_content 'Done 0'
-        end
-      end
-
-      it_behaves_like 'deleting the todo'
-      it_behaves_like 'deleting and restoring the todo'
-
-      context 'todo is stale on the page' do
-        before do
-          todos = TodosFinder.new(user, state: :pending).execute
-          TodoService.new.mark_todos_as_done(todos, user)
-        end
-
-        it_behaves_like 'deleting the todo'
-        it_behaves_like 'deleting and restoring the todo'
-      end
-    end
-
-    context 'User created todos for themself' do
-      before do
-        login_as(user)
-      end
-
-      context 'issue assigned todo' do
-        before do
-          create(:todo, :assigned, user: user, project: project, target: issue, author: user)
-          visit dashboard_todos_path
-        end
-
-        it 'shows issue assigned to yourself message' do
-          page.within('.js-todos-all')  do
-            expect(page).to have_content("You assigned issue #{issue.to_reference(full: true)} to yourself")
-          end
-        end
-      end
-
-      context 'marked todo' do
-        before do
-          create(:todo, :marked, user: user, project: project, target: issue, author: user)
-          visit dashboard_todos_path
-        end
-
-        it 'shows you added a todo message' do
-          page.within('.js-todos-all')  do
-            expect(page).to have_content("You added a todo for issue #{issue.to_reference(full: true)}")
-            expect(page).not_to have_content('to yourself')
-          end
-        end
-      end
-
-      context 'mentioned todo' do
-        before do
-          create(:todo, :mentioned, user: user, project: project, target: issue, author: user)
-          visit dashboard_todos_path
-        end
-
-        it 'shows you mentioned yourself message' do
-          page.within('.js-todos-all')  do
-            expect(page).to have_content("You mentioned yourself on issue #{issue.to_reference(full: true)}")
-            expect(page).not_to have_content('to yourself')
-          end
-        end
-      end
-
-      context 'directly_addressed todo' do
-        before do
-          create(:todo, :directly_addressed, user: user, project: project, target: issue, author: user)
-          visit dashboard_todos_path
-        end
-
-        it 'shows you directly addressed yourself message' do
-          page.within('.js-todos-all')  do
-            expect(page).to have_content("You directly addressed yourself on issue #{issue.to_reference(full: true)}")
-            expect(page).not_to have_content('to yourself')
-          end
-        end
-      end
-
-      context 'approval todo' do
-        let(:merge_request) { create(:merge_request) }
-
-        before do
-          create(:todo, :approval_required, user: user, project: project, target: merge_request, author: user)
-          visit dashboard_todos_path
-        end
-
-        it 'shows you set yourself as an approver message' do
-          page.within('.js-todos-all')  do
-            expect(page).to have_content("You set yourself as an approver for merge request #{merge_request.to_reference(full: true)}")
-            expect(page).not_to have_content('to yourself')
-          end
-        end
-      end
-    end
-
-    context 'User has done todos', js: true do
-      before do
-        create(:todo, :mentioned, :done, user: user, project: project, target: issue, author: author)
-        login_as(user)
-        visit dashboard_todos_path(state: :done)
-      end
-
-      it 'has the done todo present' do
-        expect(page).to have_selector('.todos-list .todo.todo-done', count: 1)
-      end
-
-      describe 'restoring the todo' do
-        before do
-          within first('.todo') do
-            click_link 'Add todo'
-          end
-        end
-
-        it 'is removed from the list' do
-          expect(page).not_to have_selector('.todos-list .todo.todo-done')
-        end
-
-        it 'updates todo count' do
-          expect(page).to have_content 'To do 1'
-          expect(page).to have_content 'Done 0'
-        end
-      end
-    end
-
-    context 'User has Todos with labels spanning multiple projects' do
-      before do
-        label1 = create(:label, project: project)
-        note1 = create(:note_on_issue, note: "Hello #{label1.to_reference(format: :name)}", noteable_id: issue.id, noteable_type: 'Issue', project: issue.project)
-        create(:todo, :mentioned, project: project, target: issue, user: user, note_id: note1.id)
-
-        project2 = create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC)
-        label2 = create(:label, project: project2)
-        issue2 = create(:issue, project: project2)
-        note2 = create(:note_on_issue, note: "Test #{label2.to_reference(format: :name)}", noteable_id: issue2.id, noteable_type: 'Issue', project: project2)
-        create(:todo, :mentioned, project: project2, target: issue2, user: user, note_id: note2.id)
-
-        login_as(user)
-        visit dashboard_todos_path
-      end
-
-      it 'shows page with two Todos' do
-        expect(page).to have_selector('.todos-list .todo', count: 2)
-      end
-    end
-
-    context 'User has multiple pages of Todos' do
-      before do
-        allow(Todo).to receive(:default_per_page).and_return(1)
-
-        # Create just enough records to cause us to paginate
-        create_list(:todo, 2, :mentioned, user: user, project: project, target: issue, author: author)
-
-        login_as(user)
-      end
-
-      it 'is paginated' do
-        visit dashboard_todos_path
-
-        expect(page).to have_selector('.gl-pagination')
-      end
-
-      it 'is has the right number of pages' do
-        visit dashboard_todos_path
-
-        expect(page).to have_selector('.gl-pagination .page', count: 2)
-      end
-
-      describe 'mark all as done', js: true do
-        before do
-          visit dashboard_todos_path
-          find('.js-todos-mark-all').trigger('click')
-        end
-
-        it 'shows "All done" message!' do
-          expect(page).to have_content 'To do 0'
-          expect(page).to have_content "You're all done!"
-          expect(page).not_to have_selector('.gl-pagination')
-        end
-
-        it 'shows "Undo mark all as done" button' do
-          expect(page).to have_selector('.js-todos-mark-all', visible: false)
-          expect(page).to have_selector('.js-todos-undo-all', visible: true)
-        end
-      end
-
-      describe 'undo mark all as done', js: true do
-        before do
-          visit dashboard_todos_path
-        end
-
-        it 'shows the restored todo list' do
-          mark_all_and_undo
-
-          expect(page).to have_selector('.todos-list .todo', count: 1)
-          expect(page).to have_selector('.gl-pagination')
-          expect(page).not_to have_content "You're all done!"
-        end
-
-        it 'updates todo count' do
-          mark_all_and_undo
-
-          expect(page).to have_content 'To do 2'
-          expect(page).to have_content 'Done 0'
-        end
-
-        it 'shows "Mark all as done" button' do
-          mark_all_and_undo
-
-          expect(page).to have_selector('.js-todos-mark-all', visible: true)
-          expect(page).to have_selector('.js-todos-undo-all', visible: false)
-        end
-
-        context 'User has deleted a todo' do
-          before do
-            within first('.todo') do
-              click_link 'Done'
-            end
-          end
-
-          it 'shows the restored todo list with the deleted todo' do
-            mark_all_and_undo
-
-            expect(page).to have_selector('.todos-list .todo.todo-pending', count: 1)
-          end
-        end
-
-        def mark_all_and_undo
-          find('.js-todos-mark-all').trigger('click')
-          wait_for_requests
-          find('.js-todos-undo-all').trigger('click')
-          wait_for_requests
-        end
-      end
-    end
-
-    context 'User has a Todo in a project pending deletion' do
-      before do
-        deleted_project = create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC, pending_delete: true)
-        create(:todo, :mentioned, user: user, project: deleted_project, target: issue, author: author)
-        create(:todo, :mentioned, user: user, project: deleted_project, target: issue, author: author, state: :done)
-        login_as(user)
-        visit dashboard_todos_path
-      end
-
-      it 'shows "All done" message' do
-        within('.todos-count') { expect(page).to have_content '0' }
-        expect(page).to have_content 'To do 0'
-        expect(page).to have_content 'Done 0'
-        expect(page).to have_selector('.todos-all-done', count: 1)
-      end
-    end
-
-    context 'User has a Build Failed todo' do
-      let!(:todo) { create(:todo, :build_failed, user: user, project: project, author: author) }
-
-      before do
-        login_as user
-        visit dashboard_todos_path
-      end
-
-      it 'shows the todo' do
-        expect(page).to have_content 'The build failed for merge request'
-      end
-
-      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(full: true)}", href
-      end
-    end
-  end
-end
diff --git a/spec/features/triggers_spec.rb b/spec/features/triggers_spec.rb
index 2ea9992173d223b8a029c38117a67e4eab260444..47d5f94f54e4f99ea222c97ff65bac4d24d9acd6 100644
--- a/spec/features/triggers_spec.rb
+++ b/spec/features/triggers_spec.rb
@@ -7,15 +7,14 @@ feature 'Triggers', feature: true, js: true do
   let(:guest_user) { create(:user) }
 
   before do
-    login_as(user)
-  end
+    sign_in(user)
 
-  before do
     @project = create(:empty_project)
     @project.team << [user, :master]
     @project.team << [user2, :master]
     @project.team << [guest_user, :guest]
-    visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+
+    visit project_settings_ci_cd_path(@project)
   end
 
   describe 'create trigger workflow' do
@@ -34,7 +33,7 @@ feature 'Triggers', feature: true, js: true do
       # See if "trigger creation successful" message displayed and description and owner are correct
       expect(page.find('.flash-notice')).to have_content 'Trigger was created successfully.'
       expect(page.find('.triggers-list')).to have_content 'trigger desc'
-      expect(page.find('.triggers-list .trigger-owner')).to have_content @user.name
+      expect(page.find('.triggers-list .trigger-owner')).to have_content user.name
     end
   end
 
@@ -43,7 +42,7 @@ feature 'Triggers', feature: true, js: true do
 
     scenario 'click on edit trigger opens edit trigger page' do
       create(:ci_trigger, owner: user, project: @project, description: trigger_title)
-      visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+      visit project_settings_ci_cd_path(@project)
 
       # See if edit page has correct descrption
       find('a[title="Edit"]').click
@@ -52,7 +51,7 @@ feature 'Triggers', feature: true, js: true do
 
     scenario 'edit trigger and save' do
       create(:ci_trigger, owner: user, project: @project, description: trigger_title)
-      visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+      visit project_settings_ci_cd_path(@project)
 
       # See if edit page opens, then fill in new description and save
       find('a[title="Edit"]').click
@@ -62,13 +61,13 @@ feature 'Triggers', feature: true, js: true do
       # See if "trigger updated successfully" message displayed and description and owner are correct
       expect(page.find('.flash-notice')).to have_content 'Trigger was successfully updated.'
       expect(page.find('.triggers-list')).to have_content new_trigger_title
-      expect(page.find('.triggers-list .trigger-owner')).to have_content @user.name
+      expect(page.find('.triggers-list .trigger-owner')).to have_content user.name
     end
 
     scenario 'edit "legacy" trigger and save' do
       # Create new trigger without owner association, i.e. Legacy trigger
       create(:ci_trigger, owner: nil, project: @project)
-      visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+      visit project_settings_ci_cd_path(@project)
 
       # See if the trigger can be edited and description is blank
       find('a[title="Edit"]').click
@@ -85,7 +84,7 @@ feature 'Triggers', feature: true, js: true do
   describe 'trigger "Take ownership" workflow' do
     before(:each) do
       create(:ci_trigger, owner: user2, project: @project, description: trigger_title)
-      visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+      visit project_settings_ci_cd_path(@project)
     end
 
     scenario 'button "Take ownership" has correct alert' do
@@ -99,7 +98,7 @@ feature 'Triggers', feature: true, js: true do
       page.accept_confirm do
         expect(page.find('.flash-notice')).to have_content 'Trigger was re-assigned.'
         expect(page.find('.triggers-list')).to have_content trigger_title
-        expect(page.find('.triggers-list .trigger-owner')).to have_content @user.name
+        expect(page.find('.triggers-list .trigger-owner')).to have_content user.name
       end
     end
   end
@@ -107,7 +106,7 @@ feature 'Triggers', feature: true, js: true do
   describe 'trigger "Revoke" workflow' do
     before(:each) do
       create(:ci_trigger, owner: user2, project: @project, description: trigger_title)
-      visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+      visit project_settings_ci_cd_path(@project)
     end
 
     scenario 'button "Revoke" has correct alert' do
@@ -132,7 +131,7 @@ feature 'Triggers', feature: true, js: true do
 
     scenario 'show "legacy" badge for legacy trigger' do
       create(:ci_trigger, owner: nil, project: @project)
-      visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+      visit project_settings_ci_cd_path(@project)
 
       # See if trigger without owner (i.e. legacy) shows "legacy" badge and is editable
       expect(page.find('.triggers-list')).to have_content 'legacy'
@@ -141,7 +140,7 @@ feature 'Triggers', feature: true, js: true do
 
     scenario 'show "invalid" badge for trigger with owner having insufficient permissions' do
       create(:ci_trigger, owner: guest_user, project: @project, description: trigger_title)
-      visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+      visit project_settings_ci_cd_path(@project)
 
       # See if trigger without owner (i.e. legacy) shows "legacy" badge and is non-editable
       expect(page.find('.triggers-list')).to have_content 'invalid'
@@ -151,27 +150,27 @@ feature 'Triggers', feature: true, js: true do
     scenario 'do not show "Edit" or full token for not owned trigger' do
       # Create trigger with user different from current_user
       create(:ci_trigger, owner: user2, project: @project, description: trigger_title)
-      visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+      visit project_settings_ci_cd_path(@project)
 
       # See if trigger not owned by current_user shows only first few token chars and doesn't have copy-to-clipboard button
       expect(page.find('.triggers-list')).to have_content(@project.triggers.first.token[0..3])
       expect(page.find('.triggers-list')).not_to have_selector('button.btn-clipboard')
 
       # See if trigger owner name doesn't match with current_user and trigger is non-editable
-      expect(page.find('.triggers-list .trigger-owner')).not_to have_content @user.name
+      expect(page.find('.triggers-list .trigger-owner')).not_to have_content user.name
       expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]')
     end
 
     scenario 'show "Edit" and full token for owned trigger' do
       create(:ci_trigger, owner: user, project: @project, description: trigger_title)
-      visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+      visit project_settings_ci_cd_path(@project)
 
       # See if trigger shows full token and has copy-to-clipboard button
       expect(page.find('.triggers-list')).to have_content @project.triggers.first.token
       expect(page.find('.triggers-list')).to have_selector('button.btn-clipboard')
 
       # See if trigger owner name matches with current_user and is editable
-      expect(page.find('.triggers-list .trigger-owner')).to have_content @user.name
+      expect(page.find('.triggers-list .trigger-owner')).to have_content user.name
       expect(page.find('.triggers-list')).to have_selector('a[title="Edit"]')
     end
   end
diff --git a/spec/features/u2f_spec.rb b/spec/features/u2f_spec.rb
index dc21637967fdd56494553f04c0457eb174fca5f3..f3662cb184ff718233719ab841f1939c79dcd474 100644
--- a/spec/features/u2f_spec.rb
+++ b/spec/features/u2f_spec.rb
@@ -25,7 +25,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do
     let(:user) { create(:user) }
 
     before do
-      login_as(user)
+      gitlab_sign_in(user)
       user.update_attribute(:otp_required_for_login, true)
     end
 
@@ -93,10 +93,10 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do
       manage_two_factor_authentication
       u2f_device = register_u2f_device
       expect(page).to have_content('Your U2F device was registered')
-      logout
+      gitlab_sign_out
 
       # Second user
-      user = login_as(:user)
+      user = gitlab_sign_in(:user)
       user.update_attribute(:otp_required_for_login, true)
       visit profile_account_path
       manage_two_factor_authentication
@@ -147,18 +147,18 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do
 
     before do
       # Register and logout
-      login_as(user)
+      gitlab_sign_in(user)
       user.update_attribute(:otp_required_for_login, true)
       visit profile_account_path
       manage_two_factor_authentication
       @u2f_device = register_u2f_device
-      logout
+      gitlab_sign_out
     end
 
     describe "when 2FA via OTP is disabled" do
       it "allows logging in with the U2F device" do
         user.update_attribute(:otp_required_for_login, false)
-        login_with(user)
+        gitlab_sign_in(user)
 
         @u2f_device.respond_to_u2f_authentication
 
@@ -170,7 +170,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do
     describe "when 2FA via OTP is enabled" do
       it "allows logging in with the U2F device" do
         user.update_attribute(:otp_required_for_login, true)
-        login_with(user)
+        gitlab_sign_in(user)
 
         @u2f_device.respond_to_u2f_authentication
 
@@ -180,7 +180,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do
     end
 
     it 'persists remember_me value via hidden field' do
-      login_with(user, remember: true)
+      gitlab_sign_in(user, remember: true)
 
       @u2f_device.respond_to_u2f_authentication
       expect(page).to have_content('We heard back from your U2F device')
@@ -195,15 +195,15 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do
       describe "but not the current user" do
         it "does not allow logging in with that particular device" do
           # Register current user with the different U2F device
-          current_user = login_as(:user)
+          current_user = gitlab_sign_in(:user)
           current_user.update_attribute(:otp_required_for_login, true)
           visit profile_account_path
           manage_two_factor_authentication
           register_u2f_device(name: 'My other device')
-          logout
+          gitlab_sign_out
 
           # Try authenticating user with the old U2F device
-          login_as(current_user)
+          gitlab_sign_in(current_user)
           @u2f_device.respond_to_u2f_authentication
           expect(page).to have_content('We heard back from your U2F device')
           expect(page).to have_content('Authentication via U2F device failed')
@@ -213,15 +213,15 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do
       describe "and also the current user" do
         it "allows logging in with that particular device" do
           # Register current user with the same U2F device
-          current_user = login_as(:user)
+          current_user = gitlab_sign_in(:user)
           current_user.update_attribute(:otp_required_for_login, true)
           visit profile_account_path
           manage_two_factor_authentication
           register_u2f_device(@u2f_device)
-          logout
+          gitlab_sign_out
 
           # Try authenticating user with the same U2F device
-          login_as(current_user)
+          gitlab_sign_in(current_user)
           @u2f_device.respond_to_u2f_authentication
           expect(page).to have_content('We heard back from your U2F device')
 
@@ -233,7 +233,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do
     describe "when a given U2F device has not been registered" do
       it "does not allow logging in with that particular device" do
         unregistered_device = FakeU2fDevice.new(page, 'My device')
-        login_as(user)
+        gitlab_sign_in(user)
         unregistered_device.respond_to_u2f_authentication
         expect(page).to have_content('We heard back from your U2F device')
 
@@ -244,7 +244,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do
     describe "when more than one device has been registered by the same user" do
       it "allows logging in with either device" do
         # Register first device
-        user = login_as(:user)
+        user = gitlab_sign_in(:user)
         user.update_attribute(:otp_required_for_login, true)
         visit profile_two_factor_auth_path
         expect(page).to have_content("Your U2F device needs to be set up.")
@@ -254,17 +254,17 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do
         visit profile_two_factor_auth_path
         expect(page).to have_content("Your U2F device needs to be set up.")
         second_device = register_u2f_device(name: 'My other device')
-        logout
+        gitlab_sign_out
 
         # Authenticate as both devices
         [first_device, second_device].each do |device|
-          login_as(user)
+          gitlab_sign_in(user)
           device.respond_to_u2f_authentication
           expect(page).to have_content('We heard back from your U2F device')
 
           expect(page).to have_css('.sign-out-link', visible: false)
 
-          logout
+          gitlab_sign_out
         end
       end
     end
@@ -273,7 +273,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do
       let(:user) { create(:user) }
 
       before do
-        user = login_as(:user)
+        user = gitlab_sign_in(:user)
         user.update_attribute(:otp_required_for_login, true)
         visit profile_account_path
         manage_two_factor_authentication
@@ -300,15 +300,15 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do
 
     before do
       # Register and logout
-      login_as(user)
+      gitlab_sign_in(user)
       user.update_attribute(:otp_required_for_login, true)
       visit profile_account_path
     end
 
     describe 'when no u2f device is registered' do
       before do
-        logout
-        login_with(user)
+        gitlab_sign_out
+        gitlab_sign_in(user)
       end
 
       it 'shows the fallback otp code UI' do
@@ -320,8 +320,8 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do
       before do
         manage_two_factor_authentication
         @u2f_device = register_u2f_device
-        logout
-        login_with(user)
+        gitlab_sign_out
+        gitlab_sign_in(user)
       end
 
       it 'provides a button that shows the fallback otp code UI' do
diff --git a/spec/features/unsubscribe_links_spec.rb b/spec/features/unsubscribe_links_spec.rb
index 0a8db15c75f331989b9c86e0284ae0f58b29598a..352f8ba70ac45c451fbee480c5f0c9c403a26f3e 100644
--- a/spec/features/unsubscribe_links_spec.rb
+++ b/spec/features/unsubscribe_links_spec.rb
@@ -57,7 +57,7 @@ describe 'Unsubscribe links', feature: true do
 
   context 'when logged in' do
     before do
-      login_as(recipient)
+      sign_in(recipient)
     end
 
     it 'unsubscribes from the issue when visiting the link from the email body' do
diff --git a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb
index d9d6f2e2382d6fd2f37b6ce58c0c467723346cc1..797b7b3d50d0d2f07f21340a3f5dbe1d48441578 100644
--- a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb
+++ b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb
@@ -5,7 +5,7 @@ feature 'User uploads avatar to group', feature: true do
     user = create(:user)
     group = create(:group)
     group.add_owner(user)
-    login_as(user)
+    gitlab_sign_in(user)
 
     visit edit_group_path(group)
     attach_file(
diff --git a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb
index eb8dbd76aab8c9d04f0284a466b75d2e90e9a248..a3f8027f4da582d8bdeebb3d1ca722a3c7dc8f51 100644
--- a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb
+++ b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 feature 'User uploads avatar to profile', feature: true do
   scenario 'they see their new avatar' do
     user = create(:user)
-    login_as(user)
+    gitlab_sign_in(user)
 
     visit profile_path
     attach_file(
diff --git a/spec/features/uploads/user_uploads_file_to_note_spec.rb b/spec/features/uploads/user_uploads_file_to_note_spec.rb
index 9332d3b88d2c72a2be922579bab488e10399642c..736178897a648fd335302f682d229bf9cf857d1e 100644
--- a/spec/features/uploads/user_uploads_file_to_note_spec.rb
+++ b/spec/features/uploads/user_uploads_file_to_note_spec.rb
@@ -8,8 +8,8 @@ feature 'User uploads file to note', feature: true do
   let(:issue) { create(:issue, project: project, author: user) }
 
   before do
-    login_as(user)
-    visit namespace_project_issue_path(project.namespace, project, issue)
+    gitlab_sign_in(user)
+    visit project_issue_path(project, issue)
   end
 
   context 'before uploading' do
diff --git a/spec/features/user_callout_spec.rb b/spec/features/user_callout_spec.rb
index b84f834ff1e6ab2961ba02d7c429281ca5072dd0..7538a6e4a04d329cc2f0863f7e133e64c2ef8775 100644
--- a/spec/features/user_callout_spec.rb
+++ b/spec/features/user_callout_spec.rb
@@ -6,7 +6,7 @@ describe 'User Callouts', js: true do
   let(:project) { create(:empty_project, path: 'gitlab', name: 'sample') }
 
   before do
-    login_as(user)
+    gitlab_sign_in(user)
     project.team << [user, :master]
   end
 
diff --git a/spec/features/user_can_display_performance_bar_spec.rb b/spec/features/user_can_display_performance_bar_spec.rb
index c2842255b861d2b538361292931d85b3933b8152..24fff1a3052c770cd7fb24cfada096c044973d4b 100644
--- a/spec/features/user_can_display_performance_bar_spec.rb
+++ b/spec/features/user_can_display_performance_bar_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-describe 'User can display performacne bar', :js do
+describe 'User can display performance bar', :js do
   shared_examples 'performance bar is disabled' do
     it 'does not show the performance bar by default' do
       expect(page).not_to have_css('#peek')
@@ -27,8 +27,8 @@ describe 'User can display performacne bar', :js do
         find('body').native.send_keys('pb')
       end
 
-      it 'does not show the performance bar by default' do
-        expect(page).not_to have_css('#peek')
+      it 'shows the performance bar' do
+        expect(page).to have_css('#peek')
       end
     end
   end
@@ -57,7 +57,7 @@ describe 'User can display performacne bar', :js do
 
   context 'when user is logged-in' do
     before do
-      login_as :user
+      gitlab_sign_in(create(:user))
 
       visit root_path
     end
diff --git a/spec/features/users/projects_spec.rb b/spec/features/users/projects_spec.rb
index 67ce4b444645c168efa0f1153a7b44c8d877d906..377b1a0148f11c863851ff7cfe0215fe59978336 100644
--- a/spec/features/users/projects_spec.rb
+++ b/spec/features/users/projects_spec.rb
@@ -8,7 +8,7 @@ describe 'Projects tab on a user profile', :feature, :js do
   before do
     allow(Project).to receive(:default_per_page).and_return(1)
 
-    login_as(user)
+    gitlab_sign_in(user)
 
     visit user_path(user)
 
diff --git a/spec/features/users/rss_spec.rb b/spec/features/users/rss_spec.rb
index dbd5f66b55e3f614fad1e42e2bbb7e01bf92ae94..797b317a9bb803c4befcbf8afdf483481ea42a66 100644
--- a/spec/features/users/rss_spec.rb
+++ b/spec/features/users/rss_spec.rb
@@ -5,7 +5,7 @@ feature 'User RSS' do
 
   context 'when signed in' do
     before do
-      login_as(create(:user))
+      gitlab_sign_in(create(:user))
       visit path
     end
 
diff --git a/spec/features/users/snippets_spec.rb b/spec/features/users/snippets_spec.rb
index 2e388115633f406d1e1d22727ab0ce071a18c973..74c5cbd7887d7a3cc541594fc43dfdbb4066d54a 100644
--- a/spec/features/users/snippets_spec.rb
+++ b/spec/features/users/snippets_spec.rb
@@ -24,7 +24,7 @@ describe 'Snippets tab on a user profile', feature: true, js: true do
       let!(:other_snippet) { create(:snippet, :public) }
 
       it 'contains only internal and public snippets of a user when a user is logged in' do
-        login_as(:user)
+        gitlab_sign_in(:user)
         visit user_path(user)
         page.within('.user-profile-nav') { click_link 'Snippets' }
         wait_for_requests
diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb
index c241dae12cff0aff84ba1a3d2094059a36f40cdd..84af13d3e49829aa92a01a443d1257576cfcdb59 100644
--- a/spec/features/users_spec.rb
+++ b/spec/features/users_spec.rb
@@ -24,7 +24,7 @@ feature 'Users', feature: true, js: true do
     user.reload
     expect(user.reset_password_token).not_to be_nil
 
-    login_with(user)
+    gitlab_sign_in(user)
     expect(current_path).to eq root_path
 
     user.reload
diff --git a/spec/features/variables_spec.rb b/spec/features/variables_spec.rb
index d0c982919db570fc1177b9ded6860dc1ec082d83..1a2dedf27eb63d9070ce9e07a94f8e6801b5924e 100644
--- a/spec/features/variables_spec.rb
+++ b/spec/features/variables_spec.rb
@@ -6,11 +6,11 @@ describe 'Project variables', js: true do
   let(:variable) { create(:ci_variable, key: 'test_key', value: 'test value') }
 
   before do
-    login_as(user)
+    gitlab_sign_in(user)
     project.team << [user, :master]
     project.variables << variable
 
-    visit namespace_project_settings_ci_cd_path(project.namespace, project)
+    visit project_settings_ci_cd_path(project)
   end
 
   it 'shows list of variables' do
diff --git a/spec/finders/groups_finder_spec.rb b/spec/finders/groups_finder_spec.rb
index 5b3591550c130b19f42658266f606a46931c1ace..9e70cccc3c4deac7db1ff5f1e7ba5e5878b4b6b0 100644
--- a/spec/finders/groups_finder_spec.rb
+++ b/spec/finders/groups_finder_spec.rb
@@ -38,28 +38,79 @@ describe GroupsFinder do
       end
     end
 
-    context 'subgroups' do
+    context 'subgroups', :nested_groups do
       let!(:parent_group) { create(:group, :public) }
       let!(:public_subgroup) { create(:group, :public, parent: parent_group) }
       let!(:internal_subgroup) { create(:group, :internal, parent: parent_group) }
       let!(:private_subgroup) { create(:group, :private, parent: parent_group) }
 
       context 'without a user' do
-        it 'only returns public subgroups' do
-          expect(described_class.new(nil, parent: parent_group).execute).to contain_exactly(public_subgroup)
+        it 'only returns parent and public subgroups' do
+          expect(described_class.new(nil).execute).to contain_exactly(parent_group, public_subgroup)
         end
       end
 
       context 'with a user' do
-        it 'returns public and internal subgroups' do
-          expect(described_class.new(user, parent: parent_group).execute).to contain_exactly(public_subgroup, internal_subgroup)
+        subject { described_class.new(user).execute }
+
+        it 'returns parent, public, and internal subgroups' do
+          is_expected.to contain_exactly(parent_group, public_subgroup, internal_subgroup)
         end
 
         context 'being member' do
-          it 'returns public subgroups, internal subgroups, and private subgroups user is member of' do
+          it 'returns parent, public subgroups, internal subgroups, and private subgroups user is member of' do
             private_subgroup.add_guest(user)
 
-            expect(described_class.new(user, parent: parent_group).execute).to contain_exactly(public_subgroup, internal_subgroup, private_subgroup)
+            is_expected.to contain_exactly(parent_group, public_subgroup, internal_subgroup, private_subgroup)
+          end
+        end
+
+        context 'parent group private' do
+          before do
+            parent_group.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PRIVATE)
+          end
+
+          context 'being member of parent group' do
+            it 'returns all subgroups' do
+              parent_group.add_guest(user)
+
+              is_expected.to contain_exactly(parent_group, public_subgroup, internal_subgroup, private_subgroup)
+            end
+          end
+
+          context 'authorized to private project' do
+            context 'project one level deep' do
+              let!(:subproject) { create(:empty_project, :private, namespace: private_subgroup) }
+              before do
+                subproject.add_guest(user)
+              end
+
+              it 'includes the subgroup of the project' do
+                is_expected.to include(private_subgroup)
+              end
+
+              it 'does not include private subgroups deeper down' do
+                subsubgroup = create(:group, :private, parent: private_subgroup)
+
+                is_expected.not_to include(subsubgroup)
+              end
+            end
+
+            context 'project two levels deep' do
+              let!(:private_subsubgroup) { create(:group, :private, parent: private_subgroup) }
+              let!(:subsubproject) { create(:empty_project, :private, namespace: private_subsubgroup) }
+              before do
+                subsubproject.add_guest(user)
+              end
+
+              it 'returns all the ancestor groups' do
+                is_expected.to include(private_subsubgroup, private_subgroup, parent_group)
+              end
+
+              it 'returns the groups for a given parent' do
+                expect(described_class.new(user, parent: parent_group).execute).to include(private_subgroup)
+              end
+            end
           end
         end
       end
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index 8f2d60f2f1b369818f670766f9ac8cb444a40bf5..4a52f0d5c58e33481e44bc15698154049bfcb164 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -7,9 +7,9 @@ describe IssuesFinder do
   set(:project2) { create(:empty_project) }
   set(:milestone) { create(:milestone, project: project1) }
   set(:label) { create(:label, project: project2) }
-  set(:issue1) { create(:issue, author: user, assignees: [user], project: project1, milestone: milestone, title: 'gitlab') }
+  set(:issue1) { create(:issue, author: user, assignees: [user], project: project1, milestone: milestone, title: 'gitlab', created_at: 1.week.ago) }
   set(:issue2) { create(:issue, author: user, assignees: [user], project: project2, description: 'gitlab') }
-  set(:issue3) { create(:issue, author: user2, assignees: [user2], project: project2, title: 'tanuki', description: 'tanuki') }
+  set(:issue3) { create(:issue, author: user2, assignees: [user2], project: project2, title: 'tanuki', description: 'tanuki', created_at: 1.week.from_now) }
 
   describe '#execute' do
     set(:closed_issue) { create(:issue, author: user2, assignees: [user2], project: project2, state: 'closed') }
@@ -215,6 +215,24 @@ describe IssuesFinder do
         end
       end
 
+      context 'filtering by created_at' do
+        context 'through created_after' do
+          let(:params) { { created_after: issue3.created_at } }
+
+          it 'returns issues created on or after the given date' do
+            expect(issues).to contain_exactly(issue3)
+          end
+        end
+
+        context 'through created_before' do
+          let(:params) { { created_before: issue1.created_at + 1.second } }
+
+          it 'returns issues created on or before the given date' do
+            expect(issues).to contain_exactly(issue1)
+          end
+        end
+      end
+
       context 'when the user is unauthorized' do
         let(:search_user) { nil }
 
@@ -277,22 +295,121 @@ describe IssuesFinder do
     end
   end
 
-  describe '.not_restricted_by_confidentiality' do
-    let(:authorized_user) { create(:user) }
-    let(:project) { create(:empty_project, namespace: authorized_user.namespace) }
-    let!(:public_issue) { create(:issue, project: project) }
-    let!(:confidential_issue) { create(:issue, project: project, confidential: true) }
+  describe '#with_confidentiality_access_check' do
+    let(:guest) { create(:user) }
+    set(:authorized_user) { create(:user) }
+    set(:project) { create(:empty_project, namespace: authorized_user.namespace) }
+    set(:public_issue) { create(:issue, project: project) }
+    set(:confidential_issue) { create(:issue, project: project, confidential: true) }
 
-    it 'returns non confidential issues for nil user' do
-      expect(described_class.send(:not_restricted_by_confidentiality, nil)).to include(public_issue)
-    end
+    context 'when no project filter is given' do
+      let(:params) { {} }
+
+      context 'for an anonymous user' do
+        subject { described_class.new(nil, params).with_confidentiality_access_check }
+
+        it 'returns only public issues' do
+          expect(subject).to include(public_issue)
+          expect(subject).not_to include(confidential_issue)
+        end
+      end
+
+      context 'for a user without project membership' do
+        subject { described_class.new(user, params).with_confidentiality_access_check }
 
-    it 'returns non confidential issues for user not authorized for the issues projects' do
-      expect(described_class.send(:not_restricted_by_confidentiality, user)).to include(public_issue)
+        it 'returns only public issues' do
+          expect(subject).to include(public_issue)
+          expect(subject).not_to include(confidential_issue)
+        end
+      end
+
+      context 'for a guest user' do
+        subject { described_class.new(guest, params).with_confidentiality_access_check }
+
+        before do
+          project.add_guest(guest)
+        end
+
+        it 'returns only public issues' do
+          expect(subject).to include(public_issue)
+          expect(subject).not_to include(confidential_issue)
+        end
+      end
+
+      context 'for a project member with access to view confidential issues' do
+        subject { described_class.new(authorized_user, params).with_confidentiality_access_check }
+
+        it 'returns all issues' do
+          expect(subject).to include(public_issue, confidential_issue)
+        end
+      end
     end
 
-    it 'returns all issues for user authorized for the issues projects' do
-      expect(described_class.send(:not_restricted_by_confidentiality, authorized_user)).to include(public_issue, confidential_issue)
+    context 'when searching within a specific project' do
+      let(:params) { { project_id: project.id } }
+
+      context 'for an anonymous user' do
+        subject { described_class.new(nil, params).with_confidentiality_access_check }
+
+        it 'returns only public issues' do
+          expect(subject).to include(public_issue)
+          expect(subject).not_to include(confidential_issue)
+        end
+
+        it 'does not filter by confidentiality' do
+          expect(Issue).not_to receive(:where).with(a_string_matching('confidential'), anything)
+
+          subject
+        end
+      end
+
+      context 'for a user without project membership' do
+        subject { described_class.new(user, params).with_confidentiality_access_check }
+
+        it 'returns only public issues' do
+          expect(subject).to include(public_issue)
+          expect(subject).not_to include(confidential_issue)
+        end
+
+        it 'filters by confidentiality' do
+          expect(Issue).to receive(:where).with(a_string_matching('confidential'), anything)
+
+          subject
+        end
+      end
+
+      context 'for a guest user' do
+        subject { described_class.new(guest, params).with_confidentiality_access_check }
+
+        before do
+          project.add_guest(guest)
+        end
+
+        it 'returns only public issues' do
+          expect(subject).to include(public_issue)
+          expect(subject).not_to include(confidential_issue)
+        end
+
+        it 'filters by confidentiality' do
+          expect(Issue).to receive(:where).with(a_string_matching('confidential'), anything)
+
+          subject
+        end
+      end
+
+      context 'for a project member with access to view confidential issues' do
+        subject { described_class.new(authorized_user, params).with_confidentiality_access_check }
+
+        it 'returns all issues' do
+          expect(subject).to include(public_issue, confidential_issue)
+        end
+
+        it 'does not filter by confidentiality' do
+          expect(Issue).not_to receive(:where).with(a_string_matching('confidential'), anything)
+
+          subject
+        end
+      end
     end
   end
 end
diff --git a/spec/finders/labels_finder_spec.rb b/spec/finders/labels_finder_spec.rb
index 1724cdba830a8f8a56b4fc7ed9746835821da91d..95d96354b7713720f5e32bcb371dcf63eb0fc8d7 100644
--- a/spec/finders/labels_finder_spec.rb
+++ b/spec/finders/labels_finder_spec.rb
@@ -49,12 +49,12 @@ describe LabelsFinder do
     end
 
     context 'filtering by group_id' do
-      it 'returns labels available for any project within the group' do
+      it 'returns labels available for any non-archived project within the group' do
         group_1.add_developer(user)
-
+        project_1.archive!
         finder = described_class.new(user, group_id: group_1.id)
 
-        expect(finder.execute).to eq [group_label_2, project_label_1, group_label_1, project_label_5]
+        expect(finder.execute).to eq [group_label_2, group_label_1, project_label_5]
       end
     end
 
diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
index 58b7cd5e09840f376151b67664086528b5ad51a7..5eb26de6c92114355ab9358740f4a89b34a70560 100644
--- a/spec/finders/merge_requests_finder_spec.rb
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -46,5 +46,47 @@ describe MergeRequestsFinder do
 
       expect(merge_requests).to contain_exactly(merge_request1)
     end
+
+    context 'with created_after and created_before params' do
+      let(:project4) { create(:empty_project, forked_from_project: project1) }
+
+      let!(:new_merge_request) do
+        create(:merge_request,
+               :simple,
+               author: user,
+               created_at: 1.week.from_now,
+               source_project: project4,
+               target_project: project1)
+      end
+
+      let!(:old_merge_request) do
+        create(:merge_request,
+               :simple,
+               author: user,
+               created_at: 1.week.ago,
+               source_project: project4,
+               target_project: project4)
+      end
+
+      before do
+        project4.add_master(user)
+      end
+
+      it 'filters by created_after' do
+        params = { project_id: project1.id, created_after: new_merge_request.created_at }
+
+        merge_requests = described_class.new(user, params).execute
+
+        expect(merge_requests).to contain_exactly(new_merge_request)
+      end
+
+      it 'filters by created_before' do
+        params = { project_id: project4.id, created_before: old_merge_request.created_at + 1.second }
+
+        merge_requests = described_class.new(user, params).execute
+
+        expect(merge_requests).to contain_exactly(old_merge_request)
+      end
+    end
   end
 end
diff --git a/spec/fixtures/api/schemas/prometheus/additional_metrics_query_result.json b/spec/fixtures/api/schemas/prometheus/additional_metrics_query_result.json
new file mode 100644
index 0000000000000000000000000000000000000000..47b5d283b8ce200ee0469975b0ab3701b8a87d20
--- /dev/null
+++ b/spec/fixtures/api/schemas/prometheus/additional_metrics_query_result.json
@@ -0,0 +1,58 @@
+{
+  "items": {
+    "properties": {
+      "group": {
+        "type": "string"
+      },
+      "metrics": {
+        "items": {
+          "properties": {
+            "queries": {
+              "items": {
+                "properties": {
+                  "query_range": {
+                    "type": "string"
+                  },
+                  "query": {
+                    "type": "string"
+                  },
+                  "result": {
+                    "type": "any"
+                  }
+                },
+                "type": "object"
+              },
+              "type": "array"
+            },
+            "title": {
+              "type": "string"
+            },
+            "weight": {
+              "type": "integer"
+            },
+            "y_label": {
+              "type": "string"
+            }
+          },
+          "type": "object"
+        },
+        "required": [
+          "metrics",
+          "title",
+          "weight"
+        ],
+        "type": "array"
+      },
+      "priority": {
+        "type": "integer"
+      }
+    },
+    "type": "object"
+  },
+  "required": [
+    "group",
+    "priority",
+    "metrics"
+  ],
+  "type": "array"
+}
\ No newline at end of file
diff --git a/spec/fixtures/emails/html_empty_link.eml b/spec/fixtures/emails/html_empty_link.eml
new file mode 100644
index 0000000000000000000000000000000000000000..1672b98b92580356bddb2a2d62ac2201e7ec8ac3
--- /dev/null
+++ b/spec/fixtures/emails/html_empty_link.eml
@@ -0,0 +1,26 @@
+
+MIME-Version: 1.0
+Received: by 10.25.161.144 with HTTP; Tue, 7 Oct 2014 22:17:17 -0700 (PDT)
+X-Originating-IP: [117.207.85.84]
+In-Reply-To: <5434c8b52bb3a_623ff09fec70f049749@discourse-app.mail>
+References: <topic/35@discourse.techapj.com>
+  <5434c8b52bb3a_623ff09fec70f049749@discourse-app.mail>
+Date: Wed, 8 Oct 2014 10:47:17 +0530
+Delivered-To: arpit@techapj.com
+Message-ID: <CAOJeqne=SJ_LwN4sb-0Y95ejc2OpreVhdmcPn0TnmwSvTCYzzQ@mail.gmail.com>
+Subject: Re: [Discourse] [Meta] Welcome to techAPJ's Discourse!
+From: Arpit Jalan <arpit@techapj.com>
+To: Discourse <mail+e1c7f2a380e33840aeb654f075490bad@arpitjalan.com>Accept-Language: en-US
+Content-Language: en-US
+X-MS-Has-Attach:
+X-MS-TNEF-Correlator:
+x-originating-ip: [134.68.31.227]
+Content-Type: multipart/alternative;
+        boundary="_000_B0DFE1BEB3739743BC9B639D0E6BC8FF217A6341IUMSSGMBX104ads_"
+MIME-Version: 1.0
+
+--_000_B0DFE1BEB3739743BC9B639D0E6BC8FF217A6341IUMSSGMBX104ads_
+Content-Type: text/html; charset="utf-8"
+
+<a name="_MailEndCompose">no brackets!</a>
+--_000_B0DFE1BEB3739743BC9B639D0E6BC8FF217A6341IUMSSGMBX104ads_--
diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb
index 51a3e91d201ecda60848681015a1a8ade6a1c5d2..58b43805705558cfa42a6cf6ed13ae19f79af512 100644
--- a/spec/fixtures/markdown.md.erb
+++ b/spec/fixtures/markdown.md.erb
@@ -166,9 +166,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
 - Issue in another project: <%= xissue.to_reference(project) %>
 - Ignored in code: `<%= issue.to_reference %>`
 - Ignored in links: [Link to <%= issue.to_reference %>](#issue-link)
-- Issue by URL: <%= urls.namespace_project_issue_url(issue.project.namespace, issue.project, issue) %>
+- Issue by URL: <%= urls.project_issue_url(issue.project, issue) %>
 - Link to issue by reference: [Issue](<%= issue.to_reference %>)
-- Link to issue by URL: [Issue](<%= urls.namespace_project_issue_url(issue.project.namespace, issue.project, issue) %>)
+- Link to issue by URL: [Issue](<%= urls.project_issue_url(issue.project, issue) %>)
 
 #### MergeRequestReferenceFilter
 
@@ -176,9 +176,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
 - Merge request in another project: <%= xmerge_request.to_reference(project) %>
 - Ignored in code: `<%= merge_request.to_reference %>`
 - Ignored in links: [Link to <%= merge_request.to_reference %>](#merge-request-link)
-- Merge request by URL: <%= urls.namespace_project_merge_request_url(merge_request.project.namespace, merge_request.project, merge_request) %>
+- Merge request by URL: <%= urls.project_merge_request_url(merge_request.project, merge_request) %>
 - Link to merge request by reference: [Merge request](<%= merge_request.to_reference %>)
-- Link to merge request by URL: [Merge request](<%= urls.namespace_project_merge_request_url(merge_request.project.namespace, merge_request.project, merge_request) %>)
+- Link to merge request by URL: [Merge request](<%= urls.project_merge_request_url(merge_request.project, merge_request) %>)
 
 #### SnippetReferenceFilter
 
@@ -186,9 +186,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
 - Snippet in another project: <%= xsnippet.to_reference(project) %>
 - Ignored in code: `<%= snippet.to_reference %>`
 - Ignored in links: [Link to <%= snippet.to_reference %>](#snippet-link)
-- Snippet by URL: <%= urls.namespace_project_snippet_url(snippet.project.namespace, snippet.project, snippet) %>
+- Snippet by URL: <%= urls.project_snippet_url(snippet.project, snippet) %>
 - Link to snippet by reference: [Snippet](<%= snippet.to_reference %>)
-- Link to snippet by URL: [Snippet](<%= urls.namespace_project_snippet_url(snippet.project.namespace, snippet.project, snippet) %>)
+- Link to snippet by URL: [Snippet](<%= urls.project_snippet_url(snippet.project, snippet) %>)
 
 #### CommitRangeReferenceFilter
 
@@ -196,9 +196,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
 - Range in another project: <%= xcommit_range.to_reference(project) %>
 - Ignored in code: `<%= commit_range.to_reference %>`
 - Ignored in links: [Link to <%= commit_range.to_reference %>](#commit-range-link)
-- Range by URL: <%= urls.namespace_project_compare_url(commit_range.project.namespace, commit_range.project, commit_range.to_param) %>
+- Range by URL: <%= urls.project_compare_url(commit_range.project, commit_range.to_param) %>
 - Link to range by reference: [Range](<%= commit_range.to_reference %>)
-- Link to range by URL: [Range](<%= urls.namespace_project_compare_url(commit_range.project.namespace, commit_range.project, commit_range.to_param) %>)
+- Link to range by URL: [Range](<%= urls.project_compare_url(commit_range.project, commit_range.to_param) %>)
 
 #### CommitReferenceFilter
 
@@ -206,9 +206,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
 - Commit in another project: <%= xcommit.to_reference(project) %>
 - Ignored in code: `<%= commit.to_reference %>`
 - Ignored in links: [Link to <%= commit.to_reference %>](#commit-link)
-- Commit by URL: <%= urls.namespace_project_commit_url(commit.project.namespace, commit.project, commit) %>
+- Commit by URL: <%= urls.project_commit_url(commit.project, commit) %>
 - Link to commit by reference: [Commit](<%= commit.to_reference %>)
-- Link to commit by URL: [Commit](<%= urls.namespace_project_commit_url(commit.project.namespace, commit.project, commit) %>)
+- Link to commit by URL: [Commit](<%= urls.project_commit_url(commit.project, commit) %>)
 
 #### LabelReferenceFilter
 
@@ -227,7 +227,7 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
 - Milestone in another project: <%= xmilestone.to_reference(project) %>
 - Ignored in code: `<%= simple_milestone.to_reference %>`
 - Ignored in links: [Link to <%= simple_milestone.to_reference %>](#milestone-link)
-- Milestone by URL: <%= urls.namespace_project_milestone_url(milestone.project.namespace, milestone.project, milestone) %>
+- Milestone by URL: <%= urls.project_milestone_url(milestone.project, milestone) %>
 - Link to milestone by URL: [Milestone](<%= milestone.to_reference %>)
 
 ### Task Lists
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index cc7f889b92719120481a4dd3ebaf5b5d909a88a9..e0cad1da86ad825c27600c48ae19b963eaae17bb 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -61,14 +61,14 @@ describe ApplicationHelper do
       project = create(:empty_project, avatar: File.open(uploaded_image_temp_path))
       avatar_url = "/uploads/system/project/avatar/#{project.id}/banana_sample.gif"
 
-      expect(helper.project_icon(project.full_path).to_s).
-        to eq "<img src=\"#{avatar_url}\" alt=\"Banana sample\" />"
+      expect(helper.project_icon(project.full_path).to_s)
+        .to eq "<img src=\"#{avatar_url}\" alt=\"Banana sample\" />"
 
       allow(ActionController::Base).to receive(:asset_host).and_return(gitlab_host)
       avatar_url = "#{gitlab_host}/uploads/system/project/avatar/#{project.id}/banana_sample.gif"
 
-      expect(helper.project_icon(project.full_path).to_s).
-        to eq "<img src=\"#{avatar_url}\" alt=\"Banana sample\" />"
+      expect(helper.project_icon(project.full_path).to_s)
+        .to eq "<img src=\"#{avatar_url}\" alt=\"Banana sample\" />"
     end
 
     it 'gives uploaded icon when present' do
@@ -76,48 +76,77 @@ describe ApplicationHelper do
 
       allow_any_instance_of(Project).to receive(:avatar_in_git).and_return(true)
 
-      avatar_url = "#{gitlab_host}#{namespace_project_avatar_path(project.namespace, project)}"
+      avatar_url = "#{gitlab_host}#{project_avatar_path(project)}"
       expect(helper.project_icon(project.full_path).to_s).to match(image_tag(avatar_url))
     end
   end
 
   describe 'avatar_icon' do
-    it 'returns an url for the avatar' do
-      user = create(:user, avatar: File.open(uploaded_image_temp_path))
-
-      avatar_url = "/uploads/system/user/avatar/#{user.id}/banana_sample.gif"
-
-      expect(helper.avatar_icon(user.email).to_s).to match(avatar_url)
-
-      allow(ActionController::Base).to receive(:asset_host).and_return(gitlab_host)
-      avatar_url = "#{gitlab_host}/uploads/system/user/avatar/#{user.id}/banana_sample.gif"
-
-      expect(helper.avatar_icon(user.email).to_s).to match(avatar_url)
-    end
-
-    it 'returns an url for the avatar with relative url' do
-      stub_config_setting(relative_url_root: '/gitlab')
-      # Must be stubbed after the stub above, and separately
-      stub_config_setting(url: Settings.send(:build_gitlab_url))
-
-      user = create(:user, avatar: File.open(uploaded_image_temp_path))
-
-      expect(helper.avatar_icon(user.email).to_s).
-        to match("/gitlab/uploads/system/user/avatar/#{user.id}/banana_sample.gif")
-    end
+    let(:user) { create(:user, avatar: File.open(uploaded_image_temp_path)) }
+
+    context 'using an email' do
+      context 'when there is a matching user' do
+        it 'returns a relative URL for the avatar' do
+          expect(helper.avatar_icon(user.email).to_s)
+            .to eq("/uploads/system/user/avatar/#{user.id}/banana_sample.gif")
+        end
+
+        context 'when an asset_host is set in the config' do
+          let(:asset_host) { 'http://assets' }
+
+          before do
+            allow(ActionController::Base).to receive(:asset_host).and_return(asset_host)
+          end
+
+          it 'returns an absolute URL on that asset host' do
+            expect(helper.avatar_icon(user.email, only_path: false).to_s)
+              .to eq("#{asset_host}/uploads/system/user/avatar/#{user.id}/banana_sample.gif")
+          end
+        end
+
+        context 'when only_path is set to false' do
+          it 'returns an absolute URL for the avatar' do
+            expect(helper.avatar_icon(user.email, only_path: false).to_s)
+              .to eq("#{gitlab_host}/uploads/system/user/avatar/#{user.id}/banana_sample.gif")
+          end
+        end
+
+        context 'when the GitLab instance is at a relative URL' do
+          before do
+            stub_config_setting(relative_url_root: '/gitlab')
+            # Must be stubbed after the stub above, and separately
+            stub_config_setting(url: Settings.send(:build_gitlab_url))
+          end
+
+          it 'returns a relative URL with the correct prefix' do
+            expect(helper.avatar_icon(user.email).to_s)
+              .to eq("/gitlab/uploads/system/user/avatar/#{user.id}/banana_sample.gif")
+          end
+        end
+      end
 
-    it 'calls gravatar_icon when no User exists with the given email' do
-      expect(helper).to receive(:gravatar_icon).with('foo@example.com', 20, 2)
+      context 'when no user exists for the email' do
+        it 'calls gravatar_icon' do
+          expect(helper).to receive(:gravatar_icon).with('foo@example.com', 20, 2)
 
-      helper.avatar_icon('foo@example.com', 20, 2)
+          helper.avatar_icon('foo@example.com', 20, 2)
+        end
+      end
     end
 
-    describe 'using a User' do
-      it 'returns an URL for the avatar' do
-        user = create(:user, avatar: File.open(uploaded_image_temp_path))
+    describe 'using a user' do
+      context 'when only_path is true' do
+        it 'returns a relative URL for the avatar' do
+          expect(helper.avatar_icon(user, only_path: true).to_s)
+            .to eq("/uploads/system/user/avatar/#{user.id}/banana_sample.gif")
+        end
+      end
 
-        expect(helper.avatar_icon(user).to_s).
-          to match("/uploads/system/user/avatar/#{user.id}/banana_sample.gif")
+      context 'when only_path is false' do
+        it 'returns an absolute URL for the avatar' do
+          expect(helper.avatar_icon(user, only_path: false).to_s)
+            .to eq("#{gitlab_host}/uploads/system/user/avatar/#{user.id}/banana_sample.gif")
+        end
       end
     end
   end
@@ -147,22 +176,22 @@ describe ApplicationHelper do
       it 'returns a valid Gravatar URL' do
         stub_config_setting(https: false)
 
-        expect(helper.gravatar_icon(user_email)).
-          to match('http://www.gravatar.com/avatar/b58c6f14d292556214bd64909bcdb118')
+        expect(helper.gravatar_icon(user_email))
+          .to match('http://www.gravatar.com/avatar/b58c6f14d292556214bd64909bcdb118')
       end
 
       it 'uses HTTPs when configured' do
         stub_config_setting(https: true)
 
-        expect(helper.gravatar_icon(user_email)).
-          to match('https://secure.gravatar.com')
+        expect(helper.gravatar_icon(user_email))
+          .to match('https://secure.gravatar.com')
       end
 
       it 'returns custom gravatar path when gravatar_url is set' do
         stub_gravatar_setting(plain_url: 'http://example.local/?s=%{size}&hash=%{hash}')
 
-        expect(gravatar_icon(user_email, 20)).
-          to eq('http://example.local/?s=40&hash=b58c6f14d292556214bd64909bcdb118')
+        expect(gravatar_icon(user_email, 20))
+          .to eq('http://example.local/?s=40&hash=b58c6f14d292556214bd64909bcdb118')
       end
 
       it 'accepts a custom size argument' do
@@ -234,8 +263,8 @@ describe ApplicationHelper do
     end
 
     it 'accepts a custom html_class' do
-      expect(element(html_class: 'custom_class').attr('class')).
-        to eq 'js-timeago custom_class'
+      expect(element(html_class: 'custom_class').attr('class'))
+        .to eq 'js-timeago custom_class'
     end
 
     it 'accepts a custom tooltip placement' do
@@ -263,7 +292,7 @@ describe ApplicationHelper do
       let(:alternate_url) { 'http://company.example.com/getting-help' }
 
       before do
-        allow(current_application_settings).to receive(:help_page_support_url) { alternate_url }
+        stub_application_setting(help_page_support_url: alternate_url)
       end
 
       it 'returns the alternate support url' do
diff --git a/spec/helpers/broadcast_messages_helper_spec.rb b/spec/helpers/broadcast_messages_helper_spec.rb
index c6e3c5c2368caddc87d3bd14811a989c03dedcb7..9bec0f9f432f5bd3ec67690b0e6d69f00881f3cf 100644
--- a/spec/helpers/broadcast_messages_helper_spec.rb
+++ b/spec/helpers/broadcast_messages_helper_spec.rb
@@ -33,8 +33,8 @@ describe BroadcastMessagesHelper do
     it 'allows custom style' do
       broadcast_message = double(color: '#f2dede', font: '#b94a48')
 
-      expect(helper.broadcast_message_style(broadcast_message)).
-        to match('background-color: #f2dede; color: #b94a48')
+      expect(helper.broadcast_message_style(broadcast_message))
+        .to match('background-color: #f2dede; color: #b94a48')
     end
   end
 
diff --git a/spec/helpers/button_helper_spec.rb b/spec/helpers/button_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..661327d4432624197bcc4951acb5aa3d6ee06c2f
--- /dev/null
+++ b/spec/helpers/button_helper_spec.rb
@@ -0,0 +1,65 @@
+require 'spec_helper'
+
+describe ButtonHelper do
+  describe 'http_clone_button' do
+    let(:user) { create(:user) }
+    let(:project) { create(:project) }
+    let(:has_tooltip_class) { 'has-tooltip' }
+
+    def element
+      element = helper.http_clone_button(project)
+
+      Nokogiri::HTML::DocumentFragment.parse(element).first_element_child
+    end
+
+    before do
+      allow(helper).to receive(:current_user).and_return(user)
+    end
+
+    context 'with internal auth enabled' do
+      context 'when user has a password' do
+        it 'shows no tooltip' do
+          expect(element.attr('class')).not_to include(has_tooltip_class)
+        end
+      end
+
+      context 'when user has password automatically set' do
+        let(:user) { create(:user, password_automatically_set: true) }
+
+        it 'shows a password tooltip' do
+          expect(element.attr('class')).to include(has_tooltip_class)
+          expect(element.attr('data-title')).to eq('Set a password on your account to pull or push via HTTP.')
+        end
+      end
+    end
+
+    context 'with internal auth disabled' do
+      before do
+        stub_application_setting(signin_enabled?: false)
+      end
+
+      context 'when user has no personal access tokens' do
+        it 'has a personal access token tooltip ' do
+          expect(element.attr('class')).to include(has_tooltip_class)
+          expect(element.attr('data-title')).to eq('Create a personal access token on your account to pull or push via HTTP.')
+        end
+      end
+
+      context 'when user has a personal access token' do
+        it 'shows no tooltip' do
+          create(:personal_access_token, user: user)
+
+          expect(element.attr('class')).not_to include(has_tooltip_class)
+        end
+      end
+    end
+
+    context 'when user is ldap user' do
+      let(:user) { create(:omniauth_user, password_automatically_set: true) }
+
+      it 'shows no tooltip' do
+        expect(element.attr('class')).not_to include(has_tooltip_class)
+      end
+    end
+  end
+end
diff --git a/spec/helpers/commits_helper_spec.rb b/spec/helpers/commits_helper_spec.rb
index a2c008790f9bf1c396d4e0116523bde237941ee0..c245bb439db0b11c25783f32b70f34cb50f61296 100644
--- a/spec/helpers/commits_helper_spec.rb
+++ b/spec/helpers/commits_helper_spec.rb
@@ -9,8 +9,8 @@ describe CommitsHelper do
         author_email: 'my@email.com" onmouseover="alert(1)'
       )
 
-      expect(helper.commit_author_link(commit)).
-        not_to include('onmouseover="alert(1)"')
+      expect(helper.commit_author_link(commit))
+        .not_to include('onmouseover="alert(1)"')
     end
   end
 
@@ -22,8 +22,8 @@ describe CommitsHelper do
         committer_email: 'my@email.com" onmouseover="alert(1)'
       )
 
-      expect(helper.commit_committer_link(commit)).
-        not_to include('onmouseover="alert(1)"')
+      expect(helper.commit_committer_link(commit))
+        .not_to include('onmouseover="alert(1)"')
     end
   end
 
diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb
index 0ac030d317143d3d47462c6d1eaa135ab4678796..0d909e6e1400f2b21d5bc0dc488155667eb20910 100644
--- a/spec/helpers/diff_helper_spec.rb
+++ b/spec/helpers/diff_helper_spec.rb
@@ -148,12 +148,21 @@ describe DiffHelper do
 
     it 'puts comments on added lines' do
       left = Gitlab::Diff::Line.new('\\nonewline', 'old-nonewline', 3, 3, 3)
-      right = Gitlab::Diff::Line.new('new line', 'add', 3, 3, 3)
+      right = Gitlab::Diff::Line.new('new line', 'new', 3, 3, 3)
 
       result = helper.parallel_diff_discussions(left, right, diff_file)
 
       expect(result).to eq([nil, 'comment'])
     end
+
+    it 'puts comments on unchanged lines' do
+      left = Gitlab::Diff::Line.new('unchanged line', nil, 3, 3, 3)
+      right = Gitlab::Diff::Line.new('unchanged line', nil, 3, 3, 3)
+
+      result = helper.parallel_diff_discussions(left, right, diff_file)
+
+      expect(result).to eq(['comment', nil])
+    end
   end
 
   describe "#diff_match_line" do
diff --git a/spec/helpers/form_helper_spec.rb b/spec/helpers/form_helper_spec.rb
index b20373a96fb4bef8a218882d4b84ab0d6916a2e3..18cf0031d5fd89a2b9f96193dc1e1d01809aaee3 100644
--- a/spec/helpers/form_helper_spec.rb
+++ b/spec/helpers/form_helper_spec.rb
@@ -11,18 +11,18 @@ describe FormHelper do
     it 'renders an alert div' do
       model = double(errors: errors_stub('Error 1'))
 
-      expect(helper.form_errors(model)).
-        to include('<div class="alert alert-danger" id="error_explanation">')
+      expect(helper.form_errors(model))
+        .to include('<div class="alert alert-danger" id="error_explanation">')
     end
 
     it 'contains a summary message' do
       single_error = double(errors: errors_stub('A'))
       multi_errors = double(errors: errors_stub('A', 'B', 'C'))
 
-      expect(helper.form_errors(single_error)).
-        to include('<h4>The form contains the following error:')
-      expect(helper.form_errors(multi_errors)).
-        to include('<h4>The form contains the following errors:')
+      expect(helper.form_errors(single_error))
+        .to include('<h4>The form contains the following error:')
+      expect(helper.form_errors(multi_errors))
+        .to include('<h4>The form contains the following errors:')
     end
 
     it 'renders each message' do
diff --git a/spec/helpers/gitlab_routing_helper_spec.rb b/spec/helpers/gitlab_routing_helper_spec.rb
index 14847d0a49e584e630cf21e9d6937d2ba41baf6f..7a522487a74b0fcf82aaf75a0619cce7d9b5b5c3 100644
--- a/spec/helpers/gitlab_routing_helper_spec.rb
+++ b/spec/helpers/gitlab_routing_helper_spec.rb
@@ -5,37 +5,37 @@ describe GitlabRoutingHelper do
     describe '#project_members_url' do
       let(:project) { build_stubbed(:empty_project) }
 
-      it { expect(project_members_url(project)).to eq namespace_project_project_members_url(project.namespace, project) }
+      it { expect(project_members_url(project)).to eq project_project_members_url(project) }
     end
 
     describe '#project_member_path' do
       let(:project_member) { create(:project_member) }
 
-      it { expect(project_member_path(project_member)).to eq namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member) }
+      it { expect(project_member_path(project_member)).to eq project_project_member_path(project_member.source, project_member) }
     end
 
     describe '#request_access_project_members_path' do
       let(:project) { build_stubbed(:empty_project) }
 
-      it { expect(request_access_project_members_path(project)).to eq request_access_namespace_project_project_members_path(project.namespace, project) }
+      it { expect(request_access_project_members_path(project)).to eq request_access_project_project_members_path(project) }
     end
 
     describe '#leave_project_members_path' do
       let(:project) { build_stubbed(:empty_project) }
 
-      it { expect(leave_project_members_path(project)).to eq leave_namespace_project_project_members_path(project.namespace, project) }
+      it { expect(leave_project_members_path(project)).to eq leave_project_project_members_path(project) }
     end
 
     describe '#approve_access_request_project_member_path' do
       let(:project_member) { create(:project_member) }
 
-      it { expect(approve_access_request_project_member_path(project_member)).to eq approve_access_request_namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member) }
+      it { expect(approve_access_request_project_member_path(project_member)).to eq approve_access_request_project_project_member_path(project_member.source, project_member) }
     end
 
     describe '#resend_invite_project_member_path' do
       let(:project_member) { create(:project_member) }
 
-      it { expect(resend_invite_project_member_path(project_member)).to eq resend_invite_namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member) }
+      it { expect(resend_invite_project_member_path(project_member)).to eq resend_invite_project_project_member_path(project_member.source, project_member) }
     end
   end
 
diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb
index 0337afa44529834b039a2b6d1fed5a273c8031a9..e3f9d9db9eb844fdbad06d7c3dad3e11534e92e3 100644
--- a/spec/helpers/groups_helper_spec.rb
+++ b/spec/helpers/groups_helper_spec.rb
@@ -1,6 +1,8 @@
 require 'spec_helper'
 
 describe GroupsHelper do
+  include ApplicationHelper
+
   describe 'group_icon' do
     avatar_file_path = File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif')
 
@@ -8,8 +10,8 @@ describe GroupsHelper do
       group = create(:group)
       group.avatar = fixture_file_upload(avatar_file_path)
       group.save!
-      expect(group_icon(group.path).to_s).
-        to match("/uploads/system/group/avatar/#{group.id}/banana_sample.gif")
+      expect(group_icon(group.path).to_s)
+        .to match("/uploads/system/group/avatar/#{group.id}/banana_sample.gif")
     end
 
     it 'gives default avatar_icon when no avatar is present' do
@@ -81,4 +83,15 @@ describe GroupsHelper do
       end
     end
   end
+
+  describe 'group_title', :nested_groups 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 'outputs the groups in the correct order' do
+      expect(helper.group_title(very_deep_nested_group)).to match(/>#{group.name}<\/a>.*>#{nested_group.name}<\/a>.*>#{deep_nested_group.name}<\/a>/)
+    end
+  end
 end
diff --git a/spec/helpers/import_helper_spec.rb b/spec/helpers/import_helper_spec.rb
index 10f293cddf599cf6fe5d2faf122e52cc776617ef..9afff47f4e9e1ec3de2b4cf58a2eae4dfd074db8 100644
--- a/spec/helpers/import_helper_spec.rb
+++ b/spec/helpers/import_helper_spec.rb
@@ -29,21 +29,21 @@ describe ImportHelper do
     context 'when provider is "github"' do
       context 'when provider does not specify a custom URL' do
         it 'uses default GitHub URL' do
-          allow(Gitlab.config.omniauth).to receive(:providers).
-          and_return([Settingslogic.new('name' => 'github')])
+          allow(Gitlab.config.omniauth).to receive(:providers)
+          .and_return([Settingslogic.new('name' => 'github')])
 
-          expect(helper.provider_project_link('github', 'octocat/Hello-World')).
-          to include('href="https://github.com/octocat/Hello-World"')
+          expect(helper.provider_project_link('github', 'octocat/Hello-World'))
+          .to include('href="https://github.com/octocat/Hello-World"')
         end
       end
 
       context 'when provider specify a custom URL' do
         it 'uses custom URL' do
-          allow(Gitlab.config.omniauth).to receive(:providers).
-          and_return([Settingslogic.new('name' => 'github', 'url' => 'https://github.company.com')])
+          allow(Gitlab.config.omniauth).to receive(:providers)
+          .and_return([Settingslogic.new('name' => 'github', 'url' => 'https://github.company.com')])
 
-          expect(helper.provider_project_link('github', 'octocat/Hello-World')).
-          to include('href="https://github.company.com/octocat/Hello-World"')
+          expect(helper.provider_project_link('github', 'octocat/Hello-World'))
+          .to include('href="https://github.company.com/octocat/Hello-World"')
         end
       end
     end
@@ -54,8 +54,8 @@ describe ImportHelper do
       end
 
       it 'uses given host' do
-        expect(helper.provider_project_link('gitea', 'octocat/Hello-World')).
-        to include('href="https://try.gitea.io/octocat/Hello-World"')
+        expect(helper.provider_project_link('gitea', 'octocat/Hello-World'))
+        .to include('href="https://try.gitea.io/octocat/Hello-World"')
       end
     end
   end
diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb
index 8fcf7f5fa15535df7b15b4b2563c9081baaf6e8f..d2e918ef0141aa0c9eba4d5702c0b03f206bd1af 100644
--- a/spec/helpers/issuables_helper_spec.rb
+++ b/spec/helpers/issuables_helper_spec.rb
@@ -40,23 +40,23 @@ describe IssuablesHelper do
       end
 
       it 'returns "Open" when state is :opened' do
-        expect(helper.issuables_state_counter_text(:issues, :opened)).
-          to eq('<span>Open</span> <span class="badge">42</span>')
+        expect(helper.issuables_state_counter_text(:issues, :opened))
+          .to eq('<span>Open</span> <span class="badge">42</span>')
       end
 
       it 'returns "Closed" when state is :closed' do
-        expect(helper.issuables_state_counter_text(:issues, :closed)).
-          to eq('<span>Closed</span> <span class="badge">42</span>')
+        expect(helper.issuables_state_counter_text(:issues, :closed))
+          .to eq('<span>Closed</span> <span class="badge">42</span>')
       end
 
       it 'returns "Merged" when state is :merged' do
-        expect(helper.issuables_state_counter_text(:merge_requests, :merged)).
-          to eq('<span>Merged</span> <span class="badge">42</span>')
+        expect(helper.issuables_state_counter_text(:merge_requests, :merged))
+          .to eq('<span>Merged</span> <span class="badge">42</span>')
       end
 
       it 'returns "All" when state is :all' do
-        expect(helper.issuables_state_counter_text(:merge_requests, :all)).
-          to eq('<span>All</span> <span class="badge">42</span>')
+        expect(helper.issuables_state_counter_text(:merge_requests, :all))
+          .to eq('<span>All</span> <span class="badge">42</span>')
       end
     end
 
@@ -77,57 +77,92 @@ describe IssuablesHelper do
         }.with_indifferent_access
       end
 
+      let(:issues_finder) { IssuesFinder.new(nil, params) }
+      let(:merge_requests_finder) { MergeRequestsFinder.new(nil, params) }
+
+      before do
+        allow(helper).to receive(:issues_finder).and_return(issues_finder)
+        allow(helper).to receive(:merge_requests_finder).and_return(merge_requests_finder)
+      end
+
       it 'returns the cached value when called for the same issuable type & with the same params' do
-        expect(helper).to receive(:params).twice.and_return(params)
-        expect(helper).to receive(:issuables_count_for_state).with(:issues, :opened).and_return(42)
+        expect(issues_finder).to receive(:count_by_state).and_return(opened: 42)
+
+        expect(helper.issuables_state_counter_text(:issues, :opened))
+          .to eq('<span>Open</span> <span class="badge">42</span>')
+
+        expect(issues_finder).not_to receive(:count_by_state)
+
+        expect(helper.issuables_state_counter_text(:issues, :opened))
+          .to eq('<span>Open</span> <span class="badge">42</span>')
+      end
+
+      it 'takes confidential status into account when searching for issues' do
+        expect(issues_finder).to receive(:count_by_state).and_return(opened: 42)
+
+        expect(helper.issuables_state_counter_text(:issues, :opened))
+          .to include('42')
+
+        expect(issues_finder).to receive(:user_cannot_see_confidential_issues?).twice.and_return(false)
+        expect(issues_finder).to receive(:count_by_state).and_return(opened: 40)
 
-        expect(helper.issuables_state_counter_text(:issues, :opened)).
-          to eq('<span>Open</span> <span class="badge">42</span>')
+        expect(helper.issuables_state_counter_text(:issues, :opened))
+          .to include('40')
+
+        expect(issues_finder).to receive(:user_can_see_all_confidential_issues?).and_return(true)
+        expect(issues_finder).to receive(:count_by_state).and_return(opened: 45)
+
+        expect(helper.issuables_state_counter_text(:issues, :opened))
+          .to include('45')
+      end
 
-        expect(helper).not_to receive(:issuables_count_for_state)
+      it 'does not take confidential status into account when searching for merge requests' do
+        expect(merge_requests_finder).to receive(:count_by_state).and_return(opened: 42)
+        expect(merge_requests_finder).not_to receive(:user_cannot_see_confidential_issues?)
+        expect(merge_requests_finder).not_to receive(:user_can_see_all_confidential_issues?)
 
-        expect(helper.issuables_state_counter_text(:issues, :opened)).
-          to eq('<span>Open</span> <span class="badge">42</span>')
+        expect(helper.issuables_state_counter_text(:merge_requests, :opened))
+          .to include('42')
       end
 
       it 'does not take some keys into account in the cache key' do
-        expect(helper).to receive(:params).and_return({
+        expect(issues_finder).to receive(:count_by_state).and_return(opened: 42)
+        expect(issues_finder).to receive(:params).and_return({
           author_id: '11',
           state: 'foo',
           sort: 'foo',
           utf8: 'foo',
           page: 'foo'
         }.with_indifferent_access)
-        expect(helper).to receive(:issuables_count_for_state).with(:issues, :opened).and_return(42)
 
-        expect(helper.issuables_state_counter_text(:issues, :opened)).
-          to eq('<span>Open</span> <span class="badge">42</span>')
+        expect(helper.issuables_state_counter_text(:issues, :opened))
+          .to eq('<span>Open</span> <span class="badge">42</span>')
 
-        expect(helper).to receive(:params).and_return({
+        expect(issues_finder).not_to receive(:count_by_state)
+        expect(issues_finder).to receive(:params).and_return({
           author_id: '11',
           state: 'bar',
           sort: 'bar',
           utf8: 'bar',
           page: 'bar'
         }.with_indifferent_access)
-        expect(helper).not_to receive(:issuables_count_for_state)
 
-        expect(helper.issuables_state_counter_text(:issues, :opened)).
-          to eq('<span>Open</span> <span class="badge">42</span>')
+        expect(helper.issuables_state_counter_text(:issues, :opened))
+          .to eq('<span>Open</span> <span class="badge">42</span>')
       end
 
       it 'does not take params order into account in the cache key' do
-        expect(helper).to receive(:params).and_return('author_id' => '11', 'state' => 'opened')
-        expect(helper).to receive(:issuables_count_for_state).with(:issues, :opened).and_return(42)
+        expect(issues_finder).to receive(:params).and_return('author_id' => '11', 'state' => 'opened')
+        expect(issues_finder).to receive(:count_by_state).and_return(opened: 42)
 
-        expect(helper.issuables_state_counter_text(:issues, :opened)).
-          to eq('<span>Open</span> <span class="badge">42</span>')
+        expect(helper.issuables_state_counter_text(:issues, :opened))
+          .to eq('<span>Open</span> <span class="badge">42</span>')
 
-        expect(helper).to receive(:params).and_return('state' => 'opened', 'author_id' => '11')
-        expect(helper).not_to receive(:issuables_count_for_state)
+        expect(issues_finder).to receive(:params).and_return('state' => 'opened', 'author_id' => '11')
+        expect(issues_finder).not_to receive(:count_by_state)
 
-        expect(helper.issuables_state_counter_text(:issues, :opened)).
-          to eq('<span>Open</span> <span class="badge">42</span>')
+        expect(helper.issuables_state_counter_text(:issues, :opened))
+          .to eq('<span>Open</span> <span class="badge">42</span>')
       end
     end
   end
diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb
index 540cb0ab1e025f633dd3dcd4f93aec961ef6cebc..8f7f17a484f0ff171b0589edf74366c69d668c73 100644
--- a/spec/helpers/issues_helper_spec.rb
+++ b/spec/helpers/issues_helper_spec.rb
@@ -93,8 +93,8 @@ describe IssuesHelper do
       award = build_stubbed(:award_emoji, user: build_stubbed(:user, name: 'Jane'))
       awards = Array.new(5, award).push(my_award)
 
-      expect(award_user_list(awards, current_user, limit: 2)).
-        to eq("You, Jane, and 4 more.")
+      expect(award_user_list(awards, current_user, limit: 2))
+        .to eq("You, Jane, and 4 more.")
     end
   end
 
@@ -137,7 +137,7 @@ describe IssuesHelper do
       let(:merge_request) { create(:merge_request) }
 
       it "links just the merge request" do
-        expected_path = namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request)
+        expected_path = project_merge_request_path(merge_request.project, merge_request)
 
         expect(link_to_discussions_to_resolve(merge_request, nil)).to include(expected_path)
       end
diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb
index 7cf535fadae18c96c729aec385099adafec34dd4..a8d6044fda7edd3248a6d80080bf6219054704ac 100644
--- a/spec/helpers/labels_helper_spec.rb
+++ b/spec/helpers/labels_helper_spec.rb
@@ -55,8 +55,8 @@ describe LabelsHelper do
 
     context 'without block' do
       it 'uses render_colored_label as the link content' do
-        expect(self).to receive(:render_colored_label).
-          with(label, tooltip: true).and_return('Foo')
+        expect(self).to receive(:render_colored_label)
+          .with(label, tooltip: true).and_return('Foo')
         expect(link_to_label(label)).to match('Foo')
       end
     end
diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb
index 2a0de0b06569f6877da14a2c7165813e0cd33ca8..4b6a351cf709e83c1d6ca5f8a210489dc08eb349 100644
--- a/spec/helpers/markup_helper_spec.rb
+++ b/spec/helpers/markup_helper_spec.rb
@@ -25,17 +25,17 @@ describe MarkupHelper do
       let(:actual) { "#{merge_request.to_reference} -> #{commit.to_reference} -> #{issue.to_reference}" }
 
       it "links to the merge request" do
-        expected = namespace_project_merge_request_path(project.namespace, project, merge_request)
+        expected = project_merge_request_path(project, merge_request)
         expect(helper.markdown(actual)).to match(expected)
       end
 
       it "links to the commit" do
-        expected = namespace_project_commit_path(project.namespace, project, commit)
+        expected = project_commit_path(project, commit)
         expect(helper.markdown(actual)).to match(expected)
       end
 
       it "links to the issue" do
-        expected = namespace_project_issue_path(project.namespace, project, issue)
+        expected = project_issue_path(project, issue)
         expect(helper.markdown(actual)).to match(expected)
       end
     end
@@ -46,7 +46,7 @@ describe MarkupHelper do
       let(:second_issue) { create(:issue, project: second_project) }
 
       it 'links to the issue' do
-        expected = namespace_project_issue_path(second_project.namespace, second_project, second_issue)
+        expected = project_issue_path(second_project, second_issue)
         expect(markdown(actual, project: second_project)).to match(expected)
       end
     end
@@ -68,8 +68,8 @@ describe MarkupHelper do
       expect(doc.css('a')[0].text).to eq 'This should finally fix '
 
       # First issue link
-      expect(doc.css('a')[1].attr('href')).
-        to eq namespace_project_issue_path(project.namespace, project, issues[0])
+      expect(doc.css('a')[1].attr('href'))
+        .to eq project_issue_path(project, issues[0])
       expect(doc.css('a')[1].text).to eq issues[0].to_reference
 
       # Internal commit link
@@ -77,8 +77,8 @@ describe MarkupHelper do
       expect(doc.css('a')[2].text).to eq ' and '
 
       # Second issue link
-      expect(doc.css('a')[3].attr('href')).
-        to eq namespace_project_issue_path(project.namespace, project, issues[1])
+      expect(doc.css('a')[3].attr('href'))
+        .to eq project_issue_path(project, issues[1])
       expect(doc.css('a')[3].text).to eq issues[1].to_reference
 
       # Trailing commit link
@@ -98,8 +98,8 @@ describe MarkupHelper 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, link)).
-        to match('&lt;h1&gt;test&lt;/h1&gt;')
+      expect(helper.link_to_gfm(actual, link))
+        .to match('&lt;h1&gt;test&lt;/h1&gt;')
     end
 
     it 'ignores reference links when they are the entire body' do
@@ -110,8 +110,8 @@ describe MarkupHelper do
 
     it 'replaces commit message with emoji to link' do
       actual = link_to_gfm(':book: Book', '/foo')
-      expect(actual).
-        to eq '<gl-emoji title="open book" data-name="book" data-unicode-version="6.0">📖</gl-emoji><a href="/foo"> Book</a>'
+      expect(actual)
+        .to eq '<gl-emoji title="open book" data-name="book" data-unicode-version="6.0">📖</gl-emoji><a href="/foo"> Book</a>'
     end
   end
 
diff --git a/spec/helpers/merge_requests_helper_spec.rb b/spec/helpers/merge_requests_helper_spec.rb
index f2c9d9273886471093e2d288e0676bf940ce05fa..493a4ff9a93c076f40ec1bdcf96a3ccd5345e110 100644
--- a/spec/helpers/merge_requests_helper_spec.rb
+++ b/spec/helpers/merge_requests_helper_spec.rb
@@ -15,8 +15,8 @@ describe MergeRequestsHelper do
     end
 
     it 'does not include api credentials in a link' do
-      allow(ci_service).
-        to receive(:build_page).and_return("http://secretuser:secretpass@jenkins.example.com:8888/job/test1/scm/bySHA1/12d65c")
+      allow(ci_service)
+        .to receive(:build_page).and_return("http://secretuser:secretpass@jenkins.example.com:8888/job/test1/scm/bySHA1/12d65c")
       expect(helper.ci_build_details_path(merge_request)).not_to match("secret")
     end
   end
diff --git a/spec/helpers/milestones_helper_spec.rb b/spec/helpers/milestones_helper_spec.rb
index 3cb809d42b52f357ecca23e86336e2522cd2f213..b8f9c02a4864c01f908537489b43399d1009c95f 100644
--- a/spec/helpers/milestones_helper_spec.rb
+++ b/spec/helpers/milestones_helper_spec.rb
@@ -1,6 +1,42 @@
 require 'spec_helper'
 
 describe MilestonesHelper do
+  describe '#milestones_filter_dropdown_path' do
+    let(:project) { create(:empty_project) }
+    let(:project2) { create(:empty_project) }
+    let(:group) { create(:group) }
+
+    context 'when @project present' do
+      it 'returns project milestones JSON URL' do
+        assign(:project, project)
+
+        expect(helper.milestones_filter_dropdown_path).to eq(project_milestones_path(project, :json))
+      end
+    end
+
+    context 'when @target_project present' do
+      it 'returns targeted project milestones JSON URL' do
+        assign(:target_project, project2)
+
+        expect(helper.milestones_filter_dropdown_path).to eq(project_milestones_path(project2, :json))
+      end
+    end
+
+    context 'when @group present' do
+      it 'returns group milestones JSON URL' do
+        assign(:group, group)
+
+        expect(helper.milestones_filter_dropdown_path).to eq(group_milestones_path(group, :json))
+      end
+    end
+
+    context 'when neither of @project/@target_project/@group present' do
+      it 'returns dashboard milestones JSON URL' do
+        expect(helper.milestones_filter_dropdown_path).to eq(dashboard_milestones_path(:json))
+      end
+    end
+  end
+
   describe "#milestone_date_range" do
     def result_for(*args)
       milestone_date_range(build(:milestone, *args))
diff --git a/spec/helpers/notes_helper_spec.rb b/spec/helpers/notes_helper_spec.rb
index cc861af8533b271dda801618f44979b8f1a706f4..56f252ba27347ec474a3605e09e0f01f7b6379de 100644
--- a/spec/helpers/notes_helper_spec.rb
+++ b/spec/helpers/notes_helper_spec.rb
@@ -53,7 +53,7 @@ describe NotesHelper do
           let(:discussion) { create(:diff_note_on_merge_request, noteable: merge_request, project: project).to_discussion }
 
           it 'returns the diff path with the line code' do
-            expect(helper.discussion_path(discussion)).to eq(diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, anchor: discussion.line_code))
+            expect(helper.discussion_path(discussion)).to eq(diffs_project_merge_request_path(project, merge_request, anchor: discussion.line_code))
           end
         end
 
@@ -77,7 +77,7 @@ describe NotesHelper do
           end
 
           it 'returns the diff version path with the line code' do
-            expect(helper.discussion_path(discussion)).to eq(diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, diff_id: merge_request_diff1, anchor: discussion.line_code))
+            expect(helper.discussion_path(discussion)).to eq(diffs_project_merge_request_path(project, merge_request, diff_id: merge_request_diff1, anchor: discussion.line_code))
           end
         end
 
@@ -101,7 +101,7 @@ describe NotesHelper do
           end
 
           it 'returns the diff version comparison path with the line code' do
-            expect(helper.discussion_path(discussion)).to eq(diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, diff_id: merge_request_diff3, start_sha: merge_request_diff1.head_commit_sha, anchor: discussion.line_code))
+            expect(helper.discussion_path(discussion)).to eq(diffs_project_merge_request_path(project, merge_request, diff_id: merge_request_diff3, start_sha: merge_request_diff1.head_commit_sha, anchor: discussion.line_code))
           end
         end
 
@@ -129,7 +129,7 @@ describe NotesHelper do
           end
 
           it 'returns the diff path with the line code' do
-            expect(helper.discussion_path(discussion)).to eq(diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, anchor: discussion.line_code))
+            expect(helper.discussion_path(discussion)).to eq(diffs_project_merge_request_path(project, merge_request, anchor: discussion.line_code))
           end
         end
 
@@ -160,7 +160,7 @@ describe NotesHelper do
         let(:discussion) { create(:diff_note_on_commit, project: project).to_discussion }
 
         it 'returns the commit path with the line code' do
-          expect(helper.discussion_path(discussion)).to eq(namespace_project_commit_path(project.namespace, project, commit, anchor: discussion.line_code))
+          expect(helper.discussion_path(discussion)).to eq(project_commit_path(project, commit, anchor: discussion.line_code))
         end
       end
 
@@ -168,7 +168,7 @@ describe NotesHelper do
         let(:discussion) { create(:legacy_diff_note_on_commit, project: project).to_discussion }
 
         it 'returns the commit path with the line code' do
-          expect(helper.discussion_path(discussion)).to eq(namespace_project_commit_path(project.namespace, project, commit, anchor: discussion.line_code))
+          expect(helper.discussion_path(discussion)).to eq(project_commit_path(project, commit, anchor: discussion.line_code))
         end
       end
 
@@ -176,7 +176,7 @@ describe NotesHelper do
         let(:discussion) { create(:discussion_note_on_commit, project: project).to_discussion }
 
         it 'returns the commit path' do
-          expect(helper.discussion_path(discussion)).to eq(namespace_project_commit_path(project.namespace, project, commit))
+          expect(helper.discussion_path(discussion)).to eq(project_commit_path(project, commit))
         end
       end
     end
diff --git a/spec/helpers/page_layout_helper_spec.rb b/spec/helpers/page_layout_helper_spec.rb
index dff2784f21ff1d27908f69a7e6ba3c82a605f11c..95b4032616e079fe1a3f28b2f389bea87d17888f 100644
--- a/spec/helpers/page_layout_helper_spec.rb
+++ b/spec/helpers/page_layout_helper_spec.rb
@@ -86,8 +86,8 @@ describe PageLayoutHelper do
     it 'raises ArgumentError when given more than two attributes' do
       map = { foo: 'foo', bar: 'bar', baz: 'baz' }
 
-      expect { helper.page_card_attributes(map) }.
-        to raise_error(ArgumentError, /more than two attributes/)
+      expect { helper.page_card_attributes(map) }
+        .to raise_error(ArgumentError, /more than two attributes/)
     end
 
     it 'rejects blank values' do
diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb
index 2c0e9975f73f8d933c65dbdf10ccfd4025e5c365..a04c87b08eb77c1f8c06411c2c5d4c622bb2d125 100644
--- a/spec/helpers/preferences_helper_spec.rb
+++ b/spec/helpers/preferences_helper_spec.rb
@@ -29,15 +29,15 @@ describe PreferencesHelper do
   describe 'user_color_scheme' do
     context 'with a user' do
       it "returns user's scheme's css_class" do
-        allow(helper).to receive(:current_user).
-          and_return(double(color_scheme_id: 3))
+        allow(helper).to receive(:current_user)
+          .and_return(double(color_scheme_id: 3))
 
         expect(helper.user_color_scheme).to eq 'solarized-light'
       end
 
       it 'returns the default when id is invalid' do
-        allow(helper).to receive(:current_user).
-          and_return(double(color_scheme_id: Gitlab::ColorSchemes.count + 5))
+        allow(helper).to receive(:current_user)
+          .and_return(double(color_scheme_id: Gitlab::ColorSchemes.count + 5))
       end
     end
 
@@ -45,8 +45,8 @@ describe PreferencesHelper do
       it 'returns the default theme' do
         stub_user
 
-        expect(helper.user_color_scheme).
-          to eq Gitlab::ColorSchemes.default.css_class
+        expect(helper.user_color_scheme)
+          .to eq Gitlab::ColorSchemes.default.css_class
       end
     end
   end
@@ -55,8 +55,8 @@ describe PreferencesHelper do
     if messages.empty?
       allow(helper).to receive(:current_user).and_return(nil)
     else
-      allow(helper).to receive(:current_user).
-        and_return(double('user', messages))
+      allow(helper).to receive(:current_user)
+        .and_return(double('user', messages))
     end
   end
 
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 9a4086725d270981141b9ef52be77b5514ff6ff0..487d980070715604f58a3b3eb9fe20b801c5a7aa 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -115,6 +115,82 @@ describe ProjectsHelper do
     end
   end
 
+  describe '#show_no_ssh_key_message?' do
+    let(:user) { create(:user) }
+
+    before do
+      allow(helper).to receive(:current_user).and_return(user)
+    end
+
+    context 'user has no keys' do
+      it 'returns true' do
+        expect(helper.show_no_ssh_key_message?).to be_truthy
+      end
+    end
+
+    context 'user has an ssh key' do
+      it 'returns false' do
+        create(:personal_key, user: user)
+
+        expect(helper.show_no_ssh_key_message?).to be_falsey
+      end
+    end
+  end
+
+  describe '#show_no_password_message?' do
+    let(:user) { create(:user) }
+
+    before do
+      allow(helper).to receive(:current_user).and_return(user)
+    end
+
+    context 'user has password set' do
+      it 'returns false' do
+        expect(helper.show_no_password_message?).to be_falsey
+      end
+    end
+
+    context 'user requires a password' do
+      let(:user) { create(:user, password_automatically_set: true) }
+
+      it 'returns true' do
+        expect(helper.show_no_password_message?).to be_truthy
+      end
+    end
+
+    context 'user requires a personal access token' do
+      it 'returns true' do
+        stub_application_setting(signin_enabled?: false)
+
+        expect(helper.show_no_password_message?).to be_truthy
+      end
+    end
+  end
+
+  describe '#link_to_set_password' do
+    before do
+      allow(helper).to receive(:current_user).and_return(user)
+    end
+
+    context 'user requires a password' do
+      let(:user) { create(:user, password_automatically_set: true) }
+
+      it 'returns link to set a password' do
+        expect(helper.link_to_set_password).to match %r{<a href="#{edit_profile_password_path}">set a password</a>}
+      end
+    end
+
+    context 'user requires a personal access token' do
+      let(:user) { create(:user) }
+
+      it 'returns link to create a personal access token' do
+        stub_application_setting(signin_enabled?: false)
+
+        expect(helper.link_to_set_password).to match %r{<a href="#{profile_personal_access_tokens_path}">create a personal access token</a>}
+      end
+    end
+  end
+
   describe 'link_to_member' do
     let(:group)   { create(:group) }
     let(:project) { create(:empty_project, group: group) }
diff --git a/spec/helpers/submodule_helper_spec.rb b/spec/helpers/submodule_helper_spec.rb
index cb7274301171aa7c9baaf3781e887fc93281c287..9e561d0f191eea5ce4ca3de63207c375dd96f316 100644
--- a/spec/helpers/submodule_helper_spec.rb
+++ b/spec/helpers/submodule_helper_spec.rb
@@ -170,6 +170,11 @@ describe SubmoduleHelper do
         expect(result).to eq(["/#{group.path}/test", "/#{group.path}/test/tree/#{commit_id}"])
       end
 
+      it 'with trailing whitespace' do
+        result = relative_self_links('../test.git ', commit_id)
+        expect(result).to eq(["/#{group.path}/test", "/#{group.path}/test/tree/#{commit_id}"])
+      end
+
       it 'two levels down' do
         result = relative_self_links('../../test.git', commit_id)
         expect(result).to eq(["/#{group.path}/test", "/#{group.path}/test/tree/#{commit_id}"])
diff --git a/spec/initializers/8_metrics_spec.rb b/spec/initializers/8_metrics_spec.rb
index a507d7f7f2b376988b18afd2e00c4c8d7650189d..d4189f902fd71f477c18c25f233500addb85f7f0 100644
--- a/spec/initializers/8_metrics_spec.rb
+++ b/spec/initializers/8_metrics_spec.rb
@@ -1,17 +1,25 @@
 require 'spec_helper'
-require_relative '../../config/initializers/8_metrics'
 
 describe 'instrument_classes', lib: true do
   let(:config) { double(:config) }
 
+  let(:unicorn_sampler) { double(:unicorn_sampler) }
+  let(:influx_sampler) { double(:influx_sampler) }
+
   before do
     allow(config).to receive(:instrument_method)
     allow(config).to receive(:instrument_methods)
     allow(config).to receive(:instrument_instance_method)
     allow(config).to receive(:instrument_instance_methods)
+    allow(Gitlab::Metrics::UnicornSampler).to receive(:initialize_instance).and_return(unicorn_sampler)
+    allow(Gitlab::Metrics::InfluxSampler).to receive(:initialize_instance).and_return(influx_sampler)
+    allow(unicorn_sampler).to receive(:start)
+    allow(influx_sampler).to receive(:start)
+    allow(Gitlab::Application).to receive(:configure)
   end
 
   it 'can autoload and instrument all files' do
+    require_relative '../../config/initializers/8_metrics'
     expect { instrument_classes(config) }.not_to raise_error
   end
 end
diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js
index 3fc03324d1669073d533f2733d7d055f734c1385..8e0568821086d5292f6b399332de65517c940990 100644
--- a/spec/javascripts/awards_handler_spec.js
+++ b/spec/javascripts/awards_handler_spec.js
@@ -1,7 +1,7 @@
 /* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, no-unused-expressions, comma-dangle, new-parens, no-unused-vars, quotes, jasmine/no-spec-dupes, prefer-template, max-len */
 
 import Cookies from 'js-cookie';
-import AwardsHandler from '~/awards_handler';
+import loadAwardsHandler from '~/awards_handler';
 
 import '~/lib/utils/common_utils';
 
@@ -26,14 +26,13 @@ import '~/lib/utils/common_utils';
 
   describe('AwardsHandler', function() {
     preloadFixtures('issues/issue_with_comment.html.raw');
-    beforeEach(function() {
+    beforeEach(function(done) {
       loadFixtures('issues/issue_with_comment.html.raw');
-      awardsHandler = new AwardsHandler;
-      spyOn(awardsHandler, 'postEmoji').and.callFake((function(_this) {
-        return function(button, url, emoji, cb) {
-          return cb();
-        };
-      })(this));
+      loadAwardsHandler(true).then((obj) => {
+        awardsHandler = obj;
+        spyOn(awardsHandler, 'postEmoji').and.callFake((button, url, emoji, cb) => cb());
+        done();
+      }).catch(fail);
 
       let isEmojiMenuBuilt = false;
       openAndWaitForEmojiMenu = function() {
diff --git a/spec/javascripts/behaviors/gl_emoji/unicode_support_map_spec.js b/spec/javascripts/behaviors/gl_emoji/unicode_support_map_spec.js
index 1ed96a6747813258d3795a716225d4d446f6d737..ec2c549e032dc85948dfa846c9d8a1ec6d9ee51f 100644
--- a/spec/javascripts/behaviors/gl_emoji/unicode_support_map_spec.js
+++ b/spec/javascripts/behaviors/gl_emoji/unicode_support_map_spec.js
@@ -1,4 +1,4 @@
-import { getUnicodeSupportMap } from '~/behaviors/gl_emoji/unicode_support_map';
+import getUnicodeSupportMap from '~/emoji/support/unicode_support_map';
 import AccessorUtilities from '~/lib/utils/accessor';
 
 describe('Unicode Support Map', () => {
diff --git a/spec/javascripts/behaviors/quick_submit_spec.js b/spec/javascripts/behaviors/quick_submit_spec.js
index f56b99f8a163860283523e215d623d7ca18f08f2..6dc48f9a2935cf3845b0de59e29787ddeb71977d 100644
--- a/spec/javascripts/behaviors/quick_submit_spec.js
+++ b/spec/javascripts/behaviors/quick_submit_spec.js
@@ -40,16 +40,29 @@ import '~/behaviors/quick_submit';
     it('disables input of type submit', function() {
       const submitButton = $('.js-quick-submit input[type=submit]');
       this.textarea.trigger(keydownEvent());
+
       expect(submitButton).toBeDisabled();
     });
     it('disables button of type submit', function() {
-      // button doesn't exist in fixture, add it manually
-      const submitButton = $('<button type="submit">Submit it</button>');
-      submitButton.insertAfter(this.textarea);
-
+      const submitButton = $('.js-quick-submit input[type=submit]');
       this.textarea.trigger(keydownEvent());
+
       expect(submitButton).toBeDisabled();
     });
+    it('only clicks one submit', function() {
+      const existingSubmit = $('.js-quick-submit input[type=submit]');
+      // Add an extra submit button
+      const newSubmit = $('<button type="submit">Submit it</button>');
+      newSubmit.insertAfter(this.textarea);
+
+      const oldClick = spyOnEvent(existingSubmit, 'click');
+      const newClick = spyOnEvent(newSubmit, 'click');
+
+      this.textarea.trigger(keydownEvent());
+
+      expect(oldClick).not.toHaveBeenTriggered();
+      expect(newClick).toHaveBeenTriggered();
+    });
     // We cannot stub `navigator.userAgent` for CI's `rake karma` task, so we'll
     // only run the tests that apply to the current platform
     if (navigator.userAgent.match(/Macintosh/)) {
diff --git a/spec/javascripts/boards/board_new_issue_spec.js b/spec/javascripts/boards/board_new_issue_spec.js
index 832877de71c23ca55eccc9161875f3dfeb00223e..c0a7323a505c0079d46efe7921b2e282824604c3 100644
--- a/spec/javascripts/boards/board_new_issue_spec.js
+++ b/spec/javascripts/boards/board_new_issue_spec.js
@@ -12,6 +12,7 @@ import './mock_data';
 describe('Issue boards new issue form', () => {
   let vm;
   let list;
+  let newIssueMock;
   const promiseReturn = {
     json() {
       return {
@@ -21,7 +22,11 @@ describe('Issue boards new issue form', () => {
   };
 
   const submitIssue = () => {
-    vm.$el.querySelector('.btn-success').click();
+    const dummySubmitEvent = {
+      preventDefault() {},
+    };
+    vm.$refs.submitButton = vm.$el.querySelector('.btn-success');
+    return vm.submit(dummySubmitEvent);
   };
 
   beforeEach((done) => {
@@ -32,29 +37,35 @@ describe('Issue boards new issue form', () => {
     gl.issueBoards.BoardsStore.create();
     gl.IssueBoardsApp = new Vue();
 
-    setTimeout(() => {
-      list = new List(listObj);
-
-      spyOn(gl.boardService, 'newIssue').and.callFake(() => new Promise((resolve, reject) => {
-        if (vm.title === 'error') {
-          reject();
-        } else {
-          resolve(promiseReturn);
-        }
-      }));
-
-      vm = new BoardNewIssueComp({
-        propsData: {
-          list,
-        },
-      }).$mount();
-
-      done();
-    }, 0);
+    list = new List(listObj);
+
+    newIssueMock = Promise.resolve(promiseReturn);
+    spyOn(list, 'newIssue').and.callFake(() => newIssueMock);
+
+    vm = new BoardNewIssueComp({
+      propsData: {
+        list,
+      },
+    }).$mount();
+
+    Vue.nextTick()
+      .then(done)
+      .catch(done.fail);
   });
 
-  afterEach(() => {
-    Vue.http.interceptors = _.without(Vue.http.interceptors, boardsMockInterceptor);
+  it('calls submit if submit button is clicked', (done) => {
+    spyOn(vm, 'submit');
+    vm.title = 'Testing Title';
+
+    Vue.nextTick()
+      .then(() => {
+        vm.$el.querySelector('.btn-success').click();
+
+        expect(vm.submit.calls.count()).toBe(1);
+        expect(vm.$refs['submit-button']).toBe(vm.$el.querySelector('.btn-success'));
+      })
+      .then(done)
+      .catch(done.fail);
   });
 
   it('disables submit button if title is empty', () => {
@@ -64,136 +75,122 @@ describe('Issue boards new issue form', () => {
   it('enables submit button if title is not empty', (done) => {
     vm.title = 'Testing Title';
 
-    setTimeout(() => {
-      expect(vm.$el.querySelector('.form-control').value).toBe('Testing Title');
-      expect(vm.$el.querySelector('.btn-success').disabled).not.toBe(true);
-
-      done();
-    }, 0);
+    Vue.nextTick()
+      .then(() => {
+        expect(vm.$el.querySelector('.form-control').value).toBe('Testing Title');
+        expect(vm.$el.querySelector('.btn-success').disabled).not.toBe(true);
+      })
+      .then(done)
+      .catch(done.fail);
   });
 
   it('clears title after clicking cancel', (done) => {
     vm.$el.querySelector('.btn-default').click();
 
-    setTimeout(() => {
-      expect(vm.title).toBe('');
-      done();
-    }, 0);
+    Vue.nextTick()
+      .then(() => {
+        expect(vm.title).toBe('');
+      })
+      .then(done)
+      .catch(done.fail);
   });
 
   it('does not create new issue if title is empty', (done) => {
-    submitIssue();
-
-    setTimeout(() => {
-      expect(gl.boardService.newIssue).not.toHaveBeenCalled();
-      done();
-    }, 0);
+    submitIssue()
+      .then(() => {
+        expect(list.newIssue).not.toHaveBeenCalled();
+      })
+      .then(done)
+      .catch(done.fail);
   });
 
   describe('submit success', () => {
     it('creates new issue', (done) => {
       vm.title = 'submit title';
 
-      setTimeout(() => {
-        submitIssue();
-
-        expect(gl.boardService.newIssue).toHaveBeenCalled();
-        done();
-      }, 0);
+      Vue.nextTick()
+        .then(submitIssue)
+        .then(() => {
+          expect(list.newIssue).toHaveBeenCalled();
+        })
+        .then(done)
+        .catch(done.fail);
     });
 
     it('enables button after submit', (done) => {
       vm.title = 'submit issue';
 
-      setTimeout(() => {
-        submitIssue();
-
-        expect(vm.$el.querySelector('.btn-success').disabled).toBe(false);
-        done();
-      }, 0);
+      Vue.nextTick()
+        .then(submitIssue)
+        .then(() => {
+          expect(vm.$el.querySelector('.btn-success').disabled).toBe(false);
+        })
+        .then(done)
+        .catch(done.fail);
     });
 
     it('clears title after submit', (done) => {
       vm.title = 'submit issue';
 
-      Vue.nextTick(() => {
-        submitIssue();
-
-        setTimeout(() => {
+      Vue.nextTick()
+        .then(submitIssue)
+        .then(() => {
           expect(vm.title).toBe('');
-          done();
-        }, 0);
-      });
-    });
-
-    it('adds new issue to top of list after submit request', (done) => {
-      vm.title = 'submit issue';
-
-      setTimeout(() => {
-        submitIssue();
-
-        setTimeout(() => {
-          expect(list.issues.length).toBe(2);
-          expect(list.issues[0].title).toBe('submit issue');
-          expect(list.issues[0].subscribed).toBe(true);
-          done();
-        }, 0);
-      }, 0);
+        })
+        .then(done)
+        .catch(done.fail);
     });
 
     it('sets detail issue after submit', (done) => {
       expect(gl.issueBoards.BoardsStore.detail.issue.title).toBe(undefined);
       vm.title = 'submit issue';
 
-      setTimeout(() => {
-        submitIssue();
-
-        setTimeout(() => {
+      Vue.nextTick()
+        .then(submitIssue)
+        .then(() => {
           expect(gl.issueBoards.BoardsStore.detail.issue.title).toBe('submit issue');
-          done();
-        }, 0);
-      }, 0);
+        })
+        .then(done)
+        .catch(done.fail);
     });
 
     it('sets detail list after submit', (done) => {
       vm.title = 'submit issue';
 
-      setTimeout(() => {
-        submitIssue();
-
-        setTimeout(() => {
+      Vue.nextTick()
+        .then(submitIssue)
+        .then(() => {
           expect(gl.issueBoards.BoardsStore.detail.list.id).toBe(list.id);
-          done();
-        }, 0);
-      }, 0);
+        })
+        .then(done)
+        .catch(done.fail);
     });
   });
 
   describe('submit error', () => {
-    it('removes issue', (done) => {
+    beforeEach(() => {
+      newIssueMock = Promise.reject(new Error('My hovercraft is full of eels!'));
       vm.title = 'error';
+    });
 
-      setTimeout(() => {
-        submitIssue();
-
-        setTimeout(() => {
+    it('removes issue', (done) => {
+      Vue.nextTick()
+        .then(submitIssue)
+        .then(() => {
           expect(list.issues.length).toBe(1);
-          done();
-        }, 0);
-      }, 0);
+        })
+        .then(done)
+        .catch(done.fail);
     });
 
     it('shows error', (done) => {
-      vm.title = 'error';
-
-      setTimeout(() => {
-        submitIssue();
-
-        setTimeout(() => {
+      Vue.nextTick()
+        .then(submitIssue)
+        .then(() => {
           expect(vm.error).toBe(true);
-          done();
-        }, 0);
-      }, 0);
+        })
+        .then(done)
+        .catch(done.fail);
     });
   });
 });
diff --git a/spec/javascripts/boards/list_spec.js b/spec/javascripts/boards/list_spec.js
index 8e3d9fd77a073af592cc776389b5f830750ced50..db50829a276bf5f59ae61e6065346ee1a3305fa2 100644
--- a/spec/javascripts/boards/list_spec.js
+++ b/spec/javascripts/boards/list_spec.js
@@ -150,4 +150,41 @@ describe('List model', () => {
       expect(list.getIssues).toHaveBeenCalled();
     });
   });
+
+  describe('newIssue', () => {
+    beforeEach(() => {
+      spyOn(gl.boardService, 'newIssue').and.returnValue(Promise.resolve({
+        json() {
+          return {
+            iid: 42,
+          };
+        },
+      }));
+    });
+
+    it('adds new issue to top of list', (done) => {
+      list.issues.push(new ListIssue({
+        title: 'Testing',
+        iid: _.random(10000),
+        confidential: false,
+        labels: [list.label],
+        assignees: [],
+      }));
+      const dummyIssue = new ListIssue({
+        title: 'new issue',
+        iid: _.random(10000),
+        confidential: false,
+        labels: [list.label],
+        assignees: [],
+      });
+
+      list.newIssue(dummyIssue)
+        .then(() => {
+          expect(list.issues.length).toBe(2);
+          expect(list.issues[0]).toBe(dummyIssue);
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+  });
 });
diff --git a/spec/javascripts/commit/pipelines/pipelines_spec.js b/spec/javascripts/commit/pipelines/pipelines_spec.js
index ebfd60198b28e4d28345af3c808f7caaf1780835..694f94efcffcb9e415ea7e04f67574b3393fd78f 100644
--- a/spec/javascripts/commit/pipelines/pipelines_spec.js
+++ b/spec/javascripts/commit/pipelines/pipelines_spec.js
@@ -1,15 +1,15 @@
 import Vue from 'vue';
-import PipelinesTable from '~/commit/pipelines/pipelines_table';
+import pipelinesTable from '~/commit/pipelines/pipelines_table.vue';
 
 describe('Pipelines table in Commits and Merge requests', () => {
   const jsonFixtureName = 'pipelines/pipelines.json';
   let pipeline;
+  let PipelinesTable;
 
-  preloadFixtures('static/pipelines_table.html.raw');
   preloadFixtures(jsonFixtureName);
 
   beforeEach(() => {
-    loadFixtures('static/pipelines_table.html.raw');
+    PipelinesTable = Vue.extend(pipelinesTable);
     const pipelines = getJSONFixture(jsonFixtureName).pipelines;
     pipeline = pipelines.find(p => p.id === 1);
   });
@@ -26,8 +26,11 @@ describe('Pipelines table in Commits and Merge requests', () => {
         Vue.http.interceptors.push(pipelinesEmptyResponse);
 
         this.component = new PipelinesTable({
-          el: document.querySelector('#commit-pipeline-table-view'),
-        });
+          propsData: {
+            endpoint: 'endpoint',
+            helpPagePath: 'foo',
+          },
+        }).$mount();
       });
 
       afterEach(function () {
@@ -58,8 +61,11 @@ describe('Pipelines table in Commits and Merge requests', () => {
         Vue.http.interceptors.push(pipelinesResponse);
 
         this.component = new PipelinesTable({
-          el: document.querySelector('#commit-pipeline-table-view'),
-        });
+          propsData: {
+            endpoint: 'endpoint',
+            helpPagePath: 'foo',
+          },
+        }).$mount();
       });
 
       afterEach(() => {
@@ -92,8 +98,11 @@ describe('Pipelines table in Commits and Merge requests', () => {
       Vue.http.interceptors.push(pipelinesErrorResponse);
 
       this.component = new PipelinesTable({
-        el: document.querySelector('#commit-pipeline-table-view'),
-      });
+        propsData: {
+          endpoint: 'endpoint',
+          helpPagePath: 'foo',
+        },
+      }).$mount();
     });
 
     afterEach(function () {
diff --git a/spec/javascripts/datetime_utility_spec.js b/spec/javascripts/datetime_utility_spec.js
index e54ea11b08caa2fa7a3c4714a7992b54dffe93b9..3391cade5415d5cc98dd1784781292e78a6c8ca7 100644
--- a/spec/javascripts/datetime_utility_spec.js
+++ b/spec/javascripts/datetime_utility_spec.js
@@ -16,6 +16,10 @@ import { timeIntervalInWords } from '~/lib/utils/datetime_utility';
         const date = new Date();
         date.setFullYear(date.getFullYear() + 1);
 
+        // Add a day to prevent a transient error. If date is even 1 second
+        // short of a full year, timeFor will return '11 months remaining'
+        date.setDate(date.getDate() + 1);
+
         expect(
           gl.utils.timeFor(date),
         ).toBe('1 year remaining');
diff --git a/spec/javascripts/deploy_keys/components/key_spec.js b/spec/javascripts/deploy_keys/components/key_spec.js
index a4b98f6140ddf0b86fc934a5879002f1f3598be0..5b64cbb2dfc6f06723ffe86251afb8944f332099 100644
--- a/spec/javascripts/deploy_keys/components/key_spec.js
+++ b/spec/javascripts/deploy_keys/components/key_spec.js
@@ -14,6 +14,7 @@ describe('Deploy keys key', () => {
       propsData: {
         deployKey,
         store,
+        endpoint: 'https://test.host/dummy/endpoint',
       },
     }).$mount();
   };
diff --git a/spec/javascripts/deploy_keys/components/keys_panel_spec.js b/spec/javascripts/deploy_keys/components/keys_panel_spec.js
index a69b39c35c4f1a3a14317ef26731958a1a84ddf8..08357d2b547207c09c9b205489b1f63e2ca9bd0e 100644
--- a/spec/javascripts/deploy_keys/components/keys_panel_spec.js
+++ b/spec/javascripts/deploy_keys/components/keys_panel_spec.js
@@ -17,6 +17,7 @@ describe('Deploy keys panel', () => {
         keys: data.enabled_keys,
         showHelpBox: true,
         store,
+        endpoint: 'https://test.host/dummy/endpoint',
       },
     }).$mount();
 
diff --git a/spec/javascripts/gl_emoji_spec.js b/spec/javascripts/emoji_spec.js
similarity index 98%
rename from spec/javascripts/gl_emoji_spec.js
rename to spec/javascripts/emoji_spec.js
index a09e0072fa8e1d89a3682bfba250ff0b0349642f..fa11c602ec3e799d7bd28bbe4ac79dceaf20305c 100644
--- a/spec/javascripts/gl_emoji_spec.js
+++ b/spec/javascripts/emoji_spec.js
@@ -1,12 +1,11 @@
-import { glEmojiTag } from '~/behaviors/gl_emoji';
-import {
-  isEmojiUnicodeSupported,
+import { glEmojiTag } from '~/emoji';
+import isEmojiUnicodeSupported, {
   isFlagEmoji,
   isKeycapEmoji,
   isSkinToneComboEmoji,
   isHorceRacingSkinToneComboEmoji,
   isPersonZwjEmoji,
-} from '~/behaviors/gl_emoji/is_emoji_unicode_supported';
+} from '~/emoji/support/is_emoji_unicode_supported';
 
 const emptySupportMap = {
   personZwj: false,
diff --git a/spec/javascripts/environments/environment_actions_spec.js b/spec/javascripts/environments/environment_actions_spec.js
index 596d812c724f7e22916d7da411d116246ad3fcdf..ea40a1fcd4be43b0e858ea49257f330a894ead7c 100644
--- a/spec/javascripts/environments/environment_actions_spec.js
+++ b/spec/javascripts/environments/environment_actions_spec.js
@@ -32,9 +32,16 @@ describe('Actions Component', () => {
     }).$mount();
   });
 
+  describe('computed', () => {
+    it('title', () => {
+      expect(component.title).toEqual('Deploy to...');
+    });
+  });
+
   it('should render a dropdown button with icon and title attribute', () => {
     expect(component.$el.querySelector('.fa-caret-down')).toBeDefined();
-    expect(component.$el.querySelector('.dropdown-new').getAttribute('title')).toEqual('Deploy to...');
+    expect(component.$el.querySelector('.dropdown-new').getAttribute('data-original-title')).toEqual('Deploy to...');
+    expect(component.$el.querySelector('.dropdown-new').getAttribute('aria-label')).toEqual('Deploy to...');
   });
 
   it('should render a dropdown with the provided list of actions', () => {
diff --git a/spec/javascripts/environments/environment_monitoring_spec.js b/spec/javascripts/environments/environment_monitoring_spec.js
index 0f3dba662303a8b2cc801e8a0fe972fd24f6b455..f8d8223967ae82f7be2b8d28c3143cacc92bc6c0 100644
--- a/spec/javascripts/environments/environment_monitoring_spec.js
+++ b/spec/javascripts/environments/environment_monitoring_spec.js
@@ -3,21 +3,30 @@ import monitoringComp from '~/environments/components/environment_monitoring.vue
 
 describe('Monitoring Component', () => {
   let MonitoringComponent;
+  let component;
+
+  const monitoringUrl = 'https://gitlab.com';
 
   beforeEach(() => {
     MonitoringComponent = Vue.extend(monitoringComp);
-  });
 
-  it('should render a link to environment monitoring page', () => {
-    const monitoringUrl = 'https://gitlab.com';
-    const component = new MonitoringComponent({
+    component = new MonitoringComponent({
       propsData: {
         monitoringUrl,
       },
     }).$mount();
+  });
 
+  describe('computed', () => {
+    it('title', () => {
+      expect(component.title).toEqual('Monitoring');
+    });
+  });
+
+  it('should render a link to environment monitoring page', () => {
     expect(component.$el.getAttribute('href')).toEqual(monitoringUrl);
     expect(component.$el.querySelector('.fa-area-chart')).toBeDefined();
-    expect(component.$el.getAttribute('title')).toEqual('Monitoring');
+    expect(component.$el.getAttribute('data-original-title')).toEqual('Monitoring');
+    expect(component.$el.getAttribute('aria-label')).toEqual('Monitoring');
   });
 });
diff --git a/spec/javascripts/environments/environment_stop_spec.js b/spec/javascripts/environments/environment_stop_spec.js
index 8131f1e5b116198603dbe43ac259a0df2ff514c2..3f95faf466abe66dfadcdb29b0b4954ad27dcc5b 100644
--- a/spec/javascripts/environments/environment_stop_spec.js
+++ b/spec/javascripts/environments/environment_stop_spec.js
@@ -17,8 +17,15 @@ describe('Stop Component', () => {
     }).$mount();
   });
 
+  describe('computed', () => {
+    it('title', () => {
+      expect(component.title).toEqual('Stop');
+    });
+  });
+
   it('should render a button to stop the environment', () => {
     expect(component.$el.tagName).toEqual('BUTTON');
-    expect(component.$el.getAttribute('title')).toEqual('Stop');
+    expect(component.$el.getAttribute('data-original-title')).toEqual('Stop');
+    expect(component.$el.getAttribute('aria-label')).toEqual('Stop');
   });
 });
diff --git a/spec/javascripts/environments/environment_terminal_button_spec.js b/spec/javascripts/environments/environment_terminal_button_spec.js
index 858472af4b615dbcddda82a94acd31ae59d49b8a..f1576b19d1bf59341267a70d689acdff204394e1 100644
--- a/spec/javascripts/environments/environment_terminal_button_spec.js
+++ b/spec/javascripts/environments/environment_terminal_button_spec.js
@@ -16,9 +16,16 @@ describe('Stop Component', () => {
     }).$mount();
   });
 
+  describe('computed', () => {
+    it('title', () => {
+      expect(component.title).toEqual('Terminal');
+    });
+  });
+
   it('should render a link to open a web terminal with the provided path', () => {
     expect(component.$el.tagName).toEqual('A');
-    expect(component.$el.getAttribute('title')).toEqual('Terminal');
+    expect(component.$el.getAttribute('data-original-title')).toEqual('Terminal');
+    expect(component.$el.getAttribute('aria-label')).toEqual('Terminal');
     expect(component.$el.getAttribute('href')).toEqual(terminalPath);
   });
 });
diff --git a/spec/javascripts/environments/environments_store_spec.js b/spec/javascripts/environments/environments_store_spec.js
index 6e855530b21aa8863bcf6ebeb8341239d51e65b6..f2c6ec24dd7989d604bd1b1196e4b47db504e951 100644
--- a/spec/javascripts/environments/environments_store_spec.js
+++ b/spec/javascripts/environments/environments_store_spec.js
@@ -86,6 +86,16 @@ describe('Store', () => {
       store.toggleFolder(store.state.environments[1]);
       expect(store.state.environments[1].isOpen).toEqual(false);
     });
+
+    it('should keep folder open when environments are updated', () => {
+      store.storeEnvironments(serverData);
+
+      store.toggleFolder(store.state.environments[1]);
+      expect(store.state.environments[1].isOpen).toEqual(true);
+
+      store.storeEnvironments(serverData);
+      expect(store.state.environments[1].isOpen).toEqual(true);
+    });
   });
 
   describe('setfolderContent', () => {
@@ -97,6 +107,17 @@ describe('Store', () => {
       expect(store.state.environments[1].children.length).toEqual(serverData.length);
       expect(store.state.environments[1].children[0].isChildren).toEqual(true);
     });
+
+    it('should keep folder content when environments are updated', () => {
+      store.storeEnvironments(serverData);
+
+      store.setfolderContent(store.state.environments[1], serverData);
+
+      expect(store.state.environments[1].children.length).toEqual(serverData.length);
+      // poll
+      store.storeEnvironments(serverData);
+      expect(store.state.environments[1].children.length).toEqual(serverData.length);
+    });
   });
 
   describe('store pagination', () => {
diff --git a/spec/javascripts/filtered_search/dropdown_user_spec.js b/spec/javascripts/filtered_search/dropdown_user_spec.js
index f7708301b6e62dd5587f3a1cfcda4114dfe146f2..0132f4b7c93c3ee5cc0b20ddfa1631092efe7a19 100644
--- a/spec/javascripts/filtered_search/dropdown_user_spec.js
+++ b/spec/javascripts/filtered_search/dropdown_user_spec.js
@@ -66,4 +66,38 @@ describe('Dropdown User', () => {
       window.gon = {};
     });
   });
+
+  describe('hideCurrentUser', () => {
+    const fixtureTemplate = 'issues/issue_list.html.raw';
+    preloadFixtures(fixtureTemplate);
+
+    let dropdown;
+    let authorFilterDropdownElement;
+
+    beforeEach(() => {
+      loadFixtures(fixtureTemplate);
+      authorFilterDropdownElement = document.querySelector('#js-dropdown-author');
+      const dummyInput = document.createElement('div');
+      dropdown = new gl.DropdownUser(null, authorFilterDropdownElement, dummyInput);
+    });
+
+    const findCurrentUserElement = () => authorFilterDropdownElement.querySelector('.js-current-user');
+
+    it('hides the current user from dropdown', () => {
+      const currentUserElement = findCurrentUserElement();
+      expect(currentUserElement).not.toBe(null);
+
+      dropdown.hideCurrentUser();
+
+      expect(currentUserElement.classList).toContain('hidden');
+    });
+
+    it('does nothing if no user is logged in', () => {
+      const currentUserElement = findCurrentUserElement();
+      currentUserElement.parentNode.removeChild(currentUserElement);
+      expect(findCurrentUserElement()).toBe(null);
+
+      dropdown.hideCurrentUser();
+    });
+  });
 });
diff --git a/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js b/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js
index c92a147b937c1e5540e2670f0d000c1c8ae79f45..9e2076dc383128685805a8bb84e04c262e2196b5 100644
--- a/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js
@@ -4,6 +4,10 @@ import '~/filtered_search/filtered_search_tokenizer';
 import '~/filtered_search/filtered_search_dropdown_manager';
 
 describe('Filtered Search Dropdown Manager', () => {
+  beforeEach(() => {
+    spyOn(jQuery, 'ajax');
+  });
+
   describe('addWordToInput', () => {
     function getInputValue() {
       return document.querySelector('.filtered-search').value;
diff --git a/spec/javascripts/filtered_search/filtered_search_manager_spec.js b/spec/javascripts/filtered_search/filtered_search_manager_spec.js
index 6d00d71f145476e77a48312cd6e5e5d4818b76c9..16ae649ee60b9c450476b113f7ea8119d25c18d1 100644
--- a/spec/javascripts/filtered_search/filtered_search_manager_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_manager_spec.js
@@ -1,6 +1,7 @@
 import * as recentSearchesStoreSrc from '~/filtered_search/stores/recent_searches_store';
 import RecentSearchesService from '~/filtered_search/services/recent_searches_service';
 import RecentSearchesServiceError from '~/filtered_search/services/recent_searches_service_error';
+import RecentSearchesRoot from '~/filtered_search/recent_searches_root';
 import '~/lib/utils/url_utility';
 import '~/lib/utils/common_utils';
 import '~/filtered_search/filtered_search_token_keys';
@@ -47,18 +48,23 @@ describe('Filtered Search Manager', () => {
       </div>
     `);
 
+    spyOn(gl.FilteredSearchDropdownManager.prototype, 'setDropdown').and.callFake(() => {});
+  });
+
+  const initializeManager = () => {
+    /* eslint-disable jasmine/no-unsafe-spy */
     spyOn(gl.FilteredSearchManager.prototype, 'loadSearchParamsFromURL').and.callFake(() => {});
     spyOn(gl.FilteredSearchManager.prototype, 'tokenChange').and.callFake(() => {});
-    spyOn(gl.FilteredSearchDropdownManager.prototype, 'setDropdown').and.callFake(() => {});
     spyOn(gl.FilteredSearchDropdownManager.prototype, 'updateDropdownOffset').and.callFake(() => {});
     spyOn(gl.utils, 'getParameterByName').and.returnValue(null);
     spyOn(gl.FilteredSearchVisualTokens, 'unselectTokens').and.callThrough();
+    /* eslint-enable jasmine/no-unsafe-spy */
 
     input = document.querySelector('.filtered-search');
     tokensContainer = document.querySelector('.tokens-container');
     manager = new gl.FilteredSearchManager();
     manager.setup();
-  });
+  };
 
   afterEach(() => {
     manager.cleanup();
@@ -66,32 +72,34 @@ describe('Filtered Search Manager', () => {
 
   describe('class constructor', () => {
     const isLocalStorageAvailable = 'isLocalStorageAvailable';
-    let filteredSearchManager;
 
     beforeEach(() => {
       spyOn(RecentSearchesService, 'isAvailable').and.returnValue(isLocalStorageAvailable);
       spyOn(recentSearchesStoreSrc, 'default');
-
-      filteredSearchManager = new gl.FilteredSearchManager();
-      filteredSearchManager.setup();
-
-      return filteredSearchManager;
+      spyOn(RecentSearchesRoot.prototype, 'render');
     });
 
     it('should instantiate RecentSearchesStore with isLocalStorageAvailable', () => {
+      manager = new gl.FilteredSearchManager();
+
       expect(RecentSearchesService.isAvailable).toHaveBeenCalled();
       expect(recentSearchesStoreSrc.default).toHaveBeenCalledWith({
         isLocalStorageAvailable,
         allowedKeys: gl.FilteredSearchTokenKeys.getKeys(),
       });
     });
+  });
+
+  describe('setup', () => {
+    beforeEach(() => {
+      manager = new gl.FilteredSearchManager();
+    });
 
     it('should not instantiate Flash if an RecentSearchesServiceError is caught', () => {
       spyOn(RecentSearchesService.prototype, 'fetch').and.callFake(() => Promise.reject(new RecentSearchesServiceError()));
       spyOn(window, 'Flash');
 
-      filteredSearchManager = new gl.FilteredSearchManager();
-      filteredSearchManager.setup();
+      manager.setup();
 
       expect(window.Flash).not.toHaveBeenCalled();
     });
@@ -100,10 +108,12 @@ describe('Filtered Search Manager', () => {
   describe('searchState', () => {
     beforeEach(() => {
       spyOn(gl.FilteredSearchManager.prototype, 'search').and.callFake(() => {});
+      initializeManager();
     });
 
     it('should blur button', () => {
       const e = {
+        preventDefault: () => {},
         currentTarget: {
           blur: () => {},
         },
@@ -116,6 +126,7 @@ describe('Filtered Search Manager', () => {
 
     it('should not call search if there is no state', () => {
       const e = {
+        preventDefault: () => {},
         currentTarget: {
           blur: () => {},
         },
@@ -127,6 +138,7 @@ describe('Filtered Search Manager', () => {
 
     it('should call search when there is state', () => {
       const e = {
+        preventDefault: () => {},
         currentTarget: {
           blur: () => {},
           dataset: {
@@ -143,6 +155,10 @@ describe('Filtered Search Manager', () => {
   describe('search', () => {
     const defaultParams = '?scope=all&utf8=%E2%9C%93&state=opened';
 
+    beforeEach(() => {
+      initializeManager();
+    });
+
     it('should search with a single word', (done) => {
       input.value = 'searchTerm';
 
@@ -192,6 +208,10 @@ describe('Filtered Search Manager', () => {
   });
 
   describe('handleInputPlaceholder', () => {
+    beforeEach(() => {
+      initializeManager();
+    });
+
     it('should render placeholder when there is no input', () => {
       expect(input.placeholder).toEqual(placeholder);
     });
@@ -218,6 +238,10 @@ describe('Filtered Search Manager', () => {
   });
 
   describe('checkForBackspace', () => {
+    beforeEach(() => {
+      initializeManager();
+    });
+
     describe('tokens and no input', () => {
       beforeEach(() => {
         tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
@@ -255,6 +279,10 @@ describe('Filtered Search Manager', () => {
   });
 
   describe('removeToken', () => {
+    beforeEach(() => {
+      initializeManager();
+    });
+
     it('removes token even when it is already selected', () => {
       tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
         FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', 'none', true),
@@ -286,6 +314,7 @@ describe('Filtered Search Manager', () => {
 
   describe('removeSelectedTokenKeydown', () => {
     beforeEach(() => {
+      initializeManager();
       tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
         FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', 'none', true),
       );
@@ -339,27 +368,39 @@ describe('Filtered Search Manager', () => {
       spyOn(gl.FilteredSearchVisualTokens, 'removeSelectedToken').and.callThrough();
       spyOn(gl.FilteredSearchManager.prototype, 'handleInputPlaceholder').and.callThrough();
       spyOn(gl.FilteredSearchManager.prototype, 'toggleClearSearchButton').and.callThrough();
-      manager.removeSelectedToken();
+      initializeManager();
     });
 
     it('calls FilteredSearchVisualTokens.removeSelectedToken', () => {
+      manager.removeSelectedToken();
+
       expect(gl.FilteredSearchVisualTokens.removeSelectedToken).toHaveBeenCalled();
     });
 
     it('calls handleInputPlaceholder', () => {
+      manager.removeSelectedToken();
+
       expect(manager.handleInputPlaceholder).toHaveBeenCalled();
     });
 
     it('calls toggleClearSearchButton', () => {
+      manager.removeSelectedToken();
+
       expect(manager.toggleClearSearchButton).toHaveBeenCalled();
     });
 
     it('calls update dropdown offset', () => {
+      manager.removeSelectedToken();
+
       expect(manager.dropdownManager.updateDropdownOffset).toHaveBeenCalled();
     });
   });
 
   describe('toggleInputContainerFocus', () => {
+    beforeEach(() => {
+      initializeManager();
+    });
+
     it('toggles on focus', () => {
       input.focus();
       expect(document.querySelector('.filtered-search-box').classList.contains('focus')).toEqual(true);
diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb
index a746a77654887a978b25c4b26f621a97c1656508..7e2f364ffa47f27e7d4e7d5f354cf0657cff858d 100644
--- a/spec/javascripts/fixtures/merge_requests.rb
+++ b/spec/javascripts/fixtures/merge_requests.rb
@@ -61,7 +61,8 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont
     get :show,
       namespace_id: project.namespace.to_param,
       project_id: project,
-      id: merge_request.to_param
+      id: merge_request.to_param,
+      format: :html
 
     expect(response).to be_success
     store_frontend_fixture(response, fixture_file_name)
diff --git a/spec/javascripts/fixtures/merge_requests_diffs.rb b/spec/javascripts/fixtures/merge_requests_diffs.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ac5b06ace6d64c3a7e85208ef9879046a0b91cf6
--- /dev/null
+++ b/spec/javascripts/fixtures/merge_requests_diffs.rb
@@ -0,0 +1,57 @@
+
+require 'spec_helper'
+
+describe Projects::MergeRequests::DiffsController, '(JavaScript fixtures)', type: :controller do
+  include JavaScriptFixturesHelpers
+
+  let(:admin) { create(:admin) }
+  let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+  let(:project) { create(:project, namespace: namespace, path: 'merge-requests-project') }
+  let(:merge_request) { create(:merge_request, :with_diffs, source_project: project, target_project: project, description: '- [ ] Task List Item') }
+  let(:path) { "files/ruby/popen.rb" }
+  let(:position) do
+    Gitlab::Diff::Position.new(
+      old_path: path,
+      new_path: path,
+      old_line: nil,
+      new_line: 14,
+      diff_refs: merge_request.diff_refs
+    )
+  end
+
+  render_views
+
+  before(:all) do
+    clean_frontend_fixtures('merge_request_diffs/')
+  end
+
+  before(:each) do
+    sign_in(admin)
+  end
+
+  it 'merge_request_diffs/inline_changes_tab_with_comments.json' do |example|
+    create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
+    create(:note_on_merge_request, author: admin, project: project, noteable: merge_request)
+    render_merge_request(example.description, merge_request)
+  end
+
+  it 'merge_request_diffs/parallel_changes_tab_with_comments.json' do |example|
+    create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
+    create(:note_on_merge_request, author: admin, project: project, noteable: merge_request)
+    render_merge_request(example.description, merge_request, view: 'parallel')
+  end
+
+  private
+
+  def render_merge_request(fixture_file_name, merge_request, view: 'inline')
+    get :show,
+      namespace_id: project.namespace.to_param,
+      project_id: project,
+      id: merge_request.to_param,
+      format: :json,
+      view: view
+
+    expect(response).to be_success
+    store_frontend_fixture(response, fixture_file_name)
+  end
+end
diff --git a/spec/javascripts/fixtures/pipelines_table.html.haml b/spec/javascripts/fixtures/pipelines_table.html.haml
deleted file mode 100644
index ad1682704bbec8e0260cf25b7dc07dc99fb6bf31..0000000000000000000000000000000000000000
--- a/spec/javascripts/fixtures/pipelines_table.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-#commit-pipeline-table-view{ data: { endpoint: "endpoint", "help-page-path": "foo" } }
diff --git a/spec/javascripts/fixtures/prometheus_service.rb b/spec/javascripts/fixtures/prometheus_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3200577b3262499a686fbccf6265379aa6a3bb22
--- /dev/null
+++ b/spec/javascripts/fixtures/prometheus_service.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+describe Projects::ServicesController, '(JavaScript fixtures)', type: :controller do
+  include JavaScriptFixturesHelpers
+
+  let(:admin)     { create(:admin) }
+  let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+  let(:project)   { create(:project_empty_repo, namespace: namespace, path: 'services-project') }
+  let!(:service)  { create(:prometheus_service, project: project) }
+  
+  render_views
+
+  before(:all) do
+    clean_frontend_fixtures('services/prometheus')
+  end
+
+  before(:each) do
+    sign_in(admin)
+  end
+
+  it 'services/prometheus/prometheus_service.html.raw' do |example|
+    get :edit,
+      namespace_id: namespace,
+      project_id: project,
+      id: service.to_param
+
+    expect(response).to be_success
+    store_frontend_fixture(response, example.description)
+  end
+end
diff --git a/spec/javascripts/groups/groups_spec.js b/spec/javascripts/groups/groups_spec.js
index 2a77f7259daa3f2b6943bd982fce79b614f02b3e..aaffb56fa94d14c2c4334286a38f83b5830b0305 100644
--- a/spec/javascripts/groups/groups_spec.js
+++ b/spec/javascripts/groups/groups_spec.js
@@ -1,4 +1,5 @@
 import Vue from 'vue';
+import eventHub from '~/groups/event_hub';
 import groupFolderComponent from '~/groups/components/group_folder.vue';
 import groupItemComponent from '~/groups/components/group_item.vue';
 import groupsComponent from '~/groups/components/groups.vue';
@@ -46,6 +47,12 @@ describe('Groups Component', () => {
       expect(component.$el.querySelector('#group-1120')).toBeDefined();
     });
 
+    it('should respect the order of groups', () => {
+      const wrap = component.$el.querySelector('.groups-list-tree-container > .group-list-tree');
+      expect(wrap.querySelector('.group-row:nth-child(1)').id).toBe('group-12');
+      expect(wrap.querySelector('.group-row:nth-child(2)').id).toBe('group-1119');
+    });
+
     it('should render group and its subgroup', () => {
       const lists = component.$el.querySelectorAll('.group-list-tree');
 
@@ -54,11 +61,26 @@ describe('Groups Component', () => {
       expect(lists[0].querySelector('#group-1119').classList.contains('is-open')).toBe(true);
       expect(lists[0].querySelector('#group-1119').classList.contains('has-subgroups')).toBe(true);
 
-      expect(lists[2].querySelector('#group-1120').textContent).toContain(groups[1119].subGroups[1120].name);
+      expect(lists[2].querySelector('#group-1120').textContent).toContain(groups.id1119.subGroups.id1120.name);
     });
 
     it('should remove prefix of parent group', () => {
       expect(component.$el.querySelector('#group-12 #group-1128 .title').textContent).toContain('level2 / level3 / level4');
     });
+
+    it('should remove the group after leaving the group', (done) => {
+      spyOn(window, 'confirm').and.returnValue(true);
+
+      eventHub.$on('leaveGroup', (group, collection) => {
+        store.removeGroup(group, collection);
+      });
+
+      component.$el.querySelector('#group-12 .leave-group').click();
+
+      Vue.nextTick(() => {
+        expect(component.$el.querySelector('#group-12')).toBeNull();
+        done();
+      });
+    });
   });
 });
diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js
index 2ccc4f161925fea06d6cb8531b67ed8b75a4eb80..bc13373a27eb68f2340b96be0b37387ccaa78baf 100644
--- a/spec/javascripts/issue_show/components/app_spec.js
+++ b/spec/javascripts/issue_show/components/app_spec.js
@@ -3,17 +3,9 @@ import '~/render_math';
 import '~/render_gfm';
 import issuableApp from '~/issue_show/components/app.vue';
 import eventHub from '~/issue_show/event_hub';
+import Poll from '~/lib/utils/poll';
 import issueShowData from '../mock_data';
 
-const issueShowInterceptor = data => (request, next) => {
-  next(request.respondWith(JSON.stringify(data), {
-    status: 200,
-    headers: {
-      'POLL-INTERVAL': 1,
-    },
-  }));
-};
-
 function formatText(text) {
   return text.trim().replace(/\s\s+/g, ' ');
 }
@@ -24,10 +16,10 @@ describe('Issuable output', () => {
   let vm;
 
   beforeEach(() => {
-    const IssuableDescriptionComponent = Vue.extend(issuableApp);
-    Vue.http.interceptors.push(issueShowInterceptor(issueShowData.initialRequest));
-
     spyOn(eventHub, '$emit');
+    spyOn(Poll.prototype, 'makeRequest');
+
+    const IssuableDescriptionComponent = Vue.extend(issuableApp);
 
     vm = new IssuableDescriptionComponent({
       propsData: {
@@ -50,14 +42,19 @@ describe('Issuable output', () => {
     }).$mount();
   });
 
-  afterEach(() => {
-    Vue.http.interceptors = _.without(Vue.http.interceptors, issueShowInterceptor);
-  });
-
   it('should render a title/description/edited and update title/description/edited on update', (done) => {
-    setTimeout(() => {
-      const editedText = vm.$el.querySelector('.edited-text');
+    vm.poll.options.successCallback({
+      json() {
+        return issueShowData.initialRequest;
+      },
+    });
 
+    let editedText;
+    Vue.nextTick()
+    .then(() => {
+      editedText = vm.$el.querySelector('.edited-text');
+    })
+    .then(() => {
       expect(document.querySelector('title').innerText).toContain('this is a title (#1)');
       expect(vm.$el.querySelector('.title').innerHTML).toContain('<p>this is a title</p>');
       expect(vm.$el.querySelector('.wiki').innerHTML).toContain('<p>this is a description!</p>');
@@ -65,22 +62,27 @@ describe('Issuable output', () => {
       expect(formatText(editedText.innerText)).toMatch(/Edited[\s\S]+?by Some User/);
       expect(editedText.querySelector('.author_link').href).toMatch(/\/some_user$/);
       expect(editedText.querySelector('time')).toBeTruthy();
-
-      Vue.http.interceptors.push(issueShowInterceptor(issueShowData.secondRequest));
-
-      setTimeout(() => {
-        expect(document.querySelector('title').innerText).toContain('2 (#1)');
-        expect(vm.$el.querySelector('.title').innerHTML).toContain('<p>2</p>');
-        expect(vm.$el.querySelector('.wiki').innerHTML).toContain('<p>42</p>');
-        expect(vm.$el.querySelector('.js-task-list-field').value).toContain('42');
-        expect(vm.$el.querySelector('.edited-text')).toBeTruthy();
-        expect(formatText(vm.$el.querySelector('.edited-text').innerText)).toMatch(/Edited[\s\S]+?by Other User/);
-        expect(editedText.querySelector('.author_link').href).toMatch(/\/other_user$/);
-        expect(editedText.querySelector('time')).toBeTruthy();
-
-        done();
+    })
+    .then(() => {
+      vm.poll.options.successCallback({
+        json() {
+          return issueShowData.secondRequest;
+        },
       });
-    });
+    })
+    .then(Vue.nextTick)
+    .then(() => {
+      expect(document.querySelector('title').innerText).toContain('2 (#1)');
+      expect(vm.$el.querySelector('.title').innerHTML).toContain('<p>2</p>');
+      expect(vm.$el.querySelector('.wiki').innerHTML).toContain('<p>42</p>');
+      expect(vm.$el.querySelector('.js-task-list-field').value).toContain('42');
+      expect(vm.$el.querySelector('.edited-text')).toBeTruthy();
+      expect(formatText(vm.$el.querySelector('.edited-text').innerText)).toMatch(/Edited[\s\S]+?by Other User/);
+      expect(editedText.querySelector('.author_link').href).toMatch(/\/other_user$/);
+      expect(editedText.querySelector('time')).toBeTruthy();
+    })
+    .then(done)
+    .catch(done.fail);
   });
 
   it('shows actions if permissions are correct', (done) => {
@@ -345,21 +347,23 @@ describe('Issuable output', () => {
 
   describe('open form', () => {
     it('shows locked warning if form is open & data is different', (done) => {
-      Vue.http.interceptors.push(issueShowInterceptor(issueShowData.initialRequest));
+      vm.poll.options.successCallback({
+        json() {
+          return issueShowData.initialRequest;
+        },
+      });
 
       Vue.nextTick()
-        .then(() => new Promise((resolve) => {
-          setTimeout(resolve);
-        }))
         .then(() => {
           vm.openForm();
 
-          Vue.http.interceptors.push(issueShowInterceptor(issueShowData.secondRequest));
-
-          return new Promise((resolve) => {
-            setTimeout(resolve);
+          vm.poll.options.successCallback({
+            json() {
+              return issueShowData.secondRequest;
+            },
           });
         })
+        .then(Vue.nextTick)
         .then(() => {
           expect(
             vm.formState.lockedWarningVisible,
@@ -368,9 +372,8 @@ describe('Issuable output', () => {
           expect(
             vm.$el.querySelector('.alert'),
           ).not.toBeNull();
-
-          done();
         })
+        .then(done)
         .catch(done.fail);
     });
   });
diff --git a/spec/javascripts/issue_show/components/description_spec.js b/spec/javascripts/issue_show/components/description_spec.js
index 408349cc42da656899715de46aff006cd6b43d1f..f3fdbff01a6070d8bac360933b6a16b4d8fe72d6 100644
--- a/spec/javascripts/issue_show/components/description_spec.js
+++ b/spec/javascripts/issue_show/components/description_spec.js
@@ -95,5 +95,33 @@ describe('Description component', () => {
         done();
       });
     });
+
+    it('clears task status text when no tasks are present', (done) => {
+      vm.taskStatus = '0 of 0';
+
+      setTimeout(() => {
+        expect(
+          document.querySelector('.issuable-meta #task_status').textContent.trim(),
+        ).toBe('');
+
+        done();
+      });
+    });
+  });
+
+  it('applies syntax highlighting and math when description changed', (done) => {
+    spyOn(vm, 'renderGFM').and.callThrough();
+    spyOn($.prototype, 'renderGFM').and.callThrough();
+    vm.descriptionHtml = 'changed';
+
+    Vue.nextTick(() => {
+      setTimeout(() => {
+        expect(vm.$refs['gfm-content']).toBeDefined();
+        expect(vm.renderGFM).toHaveBeenCalled();
+        expect($.prototype.renderGFM).toHaveBeenCalled();
+
+        done();
+      });
+    });
   });
 });
diff --git a/spec/javascripts/issue_show/components/fields/description_spec.js b/spec/javascripts/issue_show/components/fields/description_spec.js
index f5b35b1e8b00f59d1e7884a2a5b7208d5949c643..df8189d9290b9e2be77b06515d25b39f03b213af 100644
--- a/spec/javascripts/issue_show/components/fields/description_spec.js
+++ b/spec/javascripts/issue_show/components/fields/description_spec.js
@@ -1,6 +1,8 @@
 import Vue from 'vue';
+import eventHub from '~/issue_show/event_hub';
 import Store from '~/issue_show/stores';
 import descriptionField from '~/issue_show/components/fields/description.vue';
+import { keyboardDownEvent } from '../../helpers';
 
 describe('Description field component', () => {
   let vm;
@@ -18,6 +20,8 @@ describe('Description field component', () => {
 
     document.body.appendChild(el);
 
+    spyOn(eventHub, '$emit');
+
     vm = new Component({
       el,
       propsData: {
@@ -53,4 +57,20 @@ describe('Description field component', () => {
       document.activeElement,
     ).toBe(vm.$refs.textarea);
   });
+
+  it('triggers update with meta+enter', () => {
+    vm.$el.querySelector('.md-area textarea').dispatchEvent(keyboardDownEvent(13, true));
+
+    expect(
+      eventHub.$emit,
+    ).toHaveBeenCalled();
+  });
+
+  it('triggers update with ctrl+enter', () => {
+    vm.$el.querySelector('.md-area textarea').dispatchEvent(keyboardDownEvent(13, false, true));
+
+    expect(
+      eventHub.$emit,
+    ).toHaveBeenCalled();
+  });
 });
diff --git a/spec/javascripts/issue_show/components/fields/title_spec.js b/spec/javascripts/issue_show/components/fields/title_spec.js
index 53ae038a6a29e59d4d5d1551f9288934f6c335ee..a03b462689f1884f4805528c3f4229ce05ec99cf 100644
--- a/spec/javascripts/issue_show/components/fields/title_spec.js
+++ b/spec/javascripts/issue_show/components/fields/title_spec.js
@@ -1,6 +1,8 @@
 import Vue from 'vue';
+import eventHub from '~/issue_show/event_hub';
 import Store from '~/issue_show/stores';
 import titleField from '~/issue_show/components/fields/title.vue';
+import { keyboardDownEvent } from '../../helpers';
 
 describe('Title field component', () => {
   let vm;
@@ -15,6 +17,8 @@ describe('Title field component', () => {
     });
     store.formState.title = 'test';
 
+    spyOn(eventHub, '$emit');
+
     vm = new Component({
       propsData: {
         formState: store.formState,
@@ -27,4 +31,20 @@ describe('Title field component', () => {
       vm.$el.querySelector('.form-control').value,
     ).toBe('test');
   });
+
+  it('triggers update with meta+enter', () => {
+    vm.$el.querySelector('.form-control').dispatchEvent(keyboardDownEvent(13, true));
+
+    expect(
+      eventHub.$emit,
+    ).toHaveBeenCalled();
+  });
+
+  it('triggers update with ctrl+enter', () => {
+    vm.$el.querySelector('.form-control').dispatchEvent(keyboardDownEvent(13, false, true));
+
+    expect(
+      eventHub.$emit,
+    ).toHaveBeenCalled();
+  });
 });
diff --git a/spec/javascripts/issue_show/helpers.js b/spec/javascripts/issue_show/helpers.js
new file mode 100644
index 0000000000000000000000000000000000000000..5d2ced98ae47dac28cdb1a853289a2e9e063b431
--- /dev/null
+++ b/spec/javascripts/issue_show/helpers.js
@@ -0,0 +1,10 @@
+// eslint-disable-next-line import/prefer-default-export
+export const keyboardDownEvent = (code, metaKey = false, ctrlKey = false) => {
+  const e = new CustomEvent('keydown');
+
+  e.keyCode = code;
+  e.metaKey = metaKey;
+  e.ctrlKey = ctrlKey;
+
+  return e;
+};
diff --git a/spec/javascripts/lib/utils/dom_utils_spec.js b/spec/javascripts/lib/utils/dom_utils_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..867bf5912d1fd4c5d7150d569c6d0dac5656800b
--- /dev/null
+++ b/spec/javascripts/lib/utils/dom_utils_spec.js
@@ -0,0 +1,35 @@
+import { addClassIfElementExists } from '~/lib/utils/dom_utils';
+
+describe('DOM Utils', () => {
+  describe('addClassIfElementExists', () => {
+    const className = 'biology';
+    const fixture = `
+      <div class="parent">
+        <div class="child"></div>
+      </div>
+    `;
+
+    let parentElement;
+
+    beforeEach(() => {
+      setFixtures(fixture);
+      parentElement = document.querySelector('.parent');
+    });
+
+    it('adds class if element exists', () => {
+      const childElement = parentElement.querySelector('.child');
+      expect(childElement).not.toBe(null);
+
+      addClassIfElementExists(childElement, className);
+
+      expect(childElement.classList).toContain(className);
+    });
+
+    it('does not throw if element does not exist', () => {
+      const childElement = parentElement.querySelector('.other-child');
+      expect(childElement).toBe(null);
+
+      addClassIfElementExists(childElement, className);
+    });
+  });
+});
diff --git a/spec/javascripts/merge_request_notes_spec.js b/spec/javascripts/merge_request_notes_spec.js
index e54acfa8e4403c80453ca9f08621475c40a73648..395dc560671e5872526f1895dc845bb945f8669d 100644
--- a/spec/javascripts/merge_request_notes_spec.js
+++ b/spec/javascripts/merge_request_notes_spec.js
@@ -7,54 +7,92 @@ import '~/render_gfm';
 import '~/render_math';
 import '~/notes';
 
+const upArrowKeyCode = 38;
+
 describe('Merge request notes', () => {
   window.gon = window.gon || {};
   window.gl = window.gl || {};
   gl.utils = gl.utils || {};
 
-  const fixture = 'merge_requests/diff_comment.html.raw';
-  preloadFixtures(fixture);
+  const discussionTabFixture = 'merge_requests/diff_comment.html.raw';
+  const changesTabJsonFixture = 'merge_request_diffs/inline_changes_tab_with_comments.json';
+  preloadFixtures(discussionTabFixture, changesTabJsonFixture);
 
-  beforeEach(() => {
-    loadFixtures(fixture);
-    gl.utils.disableButtonIfEmptyField = _.noop;
-    window.project_uploads_path = 'http://test.host/uploads';
-    $('body').data('page', 'projects:merge_requests:show');
-    window.gon.current_user_id = $('.note:last').data('author-id');
+  describe('Discussion tab with diff comments', () => {
+    beforeEach(() => {
+      loadFixtures(discussionTabFixture);
+      gl.utils.disableButtonIfEmptyField = _.noop;
+      window.project_uploads_path = 'http://test.host/uploads';
+      $('body').data('page', 'projects:merge_requests:show');
+      window.gon.current_user_id = $('.note:last').data('author-id');
 
-    return new Notes('', []);
-  });
+      return new Notes('', []);
+    });
+
+    describe('up arrow', () => {
+      it('edits last comment when triggered in main form', () => {
+        const upArrowEvent = $.Event('keydown');
+        upArrowEvent.which = upArrowKeyCode;
+
+        spyOnEvent('.note:last .js-note-edit', 'click');
+
+        $('.js-note-text').trigger(upArrowEvent);
+
+        expect('click').toHaveBeenTriggeredOn('.note:last .js-note-edit');
+      });
+
+      it('edits last comment in discussion when triggered in discussion form', (done) => {
+        const upArrowEvent = $.Event('keydown');
+        upArrowEvent.which = upArrowKeyCode;
+
+        spyOnEvent('.note-discussion .js-note-edit', 'click');
+
+        $('.js-discussion-reply-button').click();
 
-  describe('up arrow', () => {
-    it('edits last comment when triggered in main form', () => {
-      const upArrowEvent = $.Event('keydown');
-      upArrowEvent.which = 38;
+        setTimeout(() => {
+          expect(
+            $('.note-discussion .js-note-text'),
+          ).toExist();
 
-      spyOnEvent('.note:last .js-note-edit', 'click');
+          $('.note-discussion .js-note-text').trigger(upArrowEvent);
 
-      $('.js-note-text').trigger(upArrowEvent);
+          expect('click').toHaveBeenTriggeredOn('.note-discussion .js-note-edit');
 
-      expect('click').toHaveBeenTriggeredOn('.note:last .js-note-edit');
+          done();
+        });
+      });
     });
+  });
 
-    it('edits last comment in discussion when triggered in discussion form', (done) => {
-      const upArrowEvent = $.Event('keydown');
-      upArrowEvent.which = 38;
+  describe('Changes tab with diff comments', () => {
+    beforeEach(() => {
+      const diffsResponse = getJSONFixture(changesTabJsonFixture);
+      const noteFormHtml = `<form class="js-new-note-form">
+        <textarea class="js-note-text"></textarea>
+      </form>`;
+      setFixtures(diffsResponse.html + noteFormHtml);
+      $('body').data('page', 'projects:merge_requests:show');
+      window.gon.current_user_id = $('.note:last').data('author-id');
+
+      return new Notes('', []);
+    });
 
-      spyOnEvent('.note-discussion .js-note-edit', 'click');
+    describe('up arrow', () => {
+      it('edits last comment in discussion when triggered in discussion form', (done) => {
+        const upArrowEvent = $.Event('keydown');
+        upArrowEvent.which = upArrowKeyCode;
 
-      $('.js-discussion-reply-button').click();
+        spyOnEvent('.note:last .js-note-edit', 'click');
 
-      setTimeout(() => {
-        expect(
-          $('.note-discussion .js-note-text'),
-        ).toExist();
+        $('.js-discussion-reply-button').trigger('click');
 
-        $('.note-discussion .js-note-text').trigger(upArrowEvent);
+        setTimeout(() => {
+          $('.js-note-text').trigger(upArrowEvent);
 
-        expect('click').toHaveBeenTriggeredOn('.note-discussion .js-note-edit');
+          expect('click').toHaveBeenTriggeredOn('.note:last .js-note-edit');
 
-        done();
+          done();
+        });
       });
     });
   });
diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js
index 9916d2c1e2160b76d2f1d3068418ee96e3f8682a..49ef21f75de0a9f6688603ef01485c01bcbf8406 100644
--- a/spec/javascripts/merge_request_tabs_spec.js
+++ b/spec/javascripts/merge_request_tabs_spec.js
@@ -22,7 +22,15 @@ import 'vendor/jquery.scrollTo';
       };
       $.extend(stubLocation, defaults, stubs || {});
     };
-    preloadFixtures('merge_requests/merge_request_with_task_list.html.raw', 'merge_requests/diff_comment.html.raw');
+
+    const inlineChangesTabJsonFixture = 'merge_request_diffs/inline_changes_tab_with_comments.json';
+    const parallelChangesTabJsonFixture = 'merge_request_diffs/parallel_changes_tab_with_comments.json';
+    preloadFixtures(
+      'merge_requests/merge_request_with_task_list.html.raw',
+      'merge_requests/diff_comment.html.raw',
+      inlineChangesTabJsonFixture,
+      parallelChangesTabJsonFixture
+    );
 
     beforeEach(function () {
       this.class = new gl.MergeRequestTabs({ stubLocation: stubLocation });
@@ -44,14 +52,10 @@ import 'vendor/jquery.scrollTo';
         loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
         this.subject = this.class.activateTab;
       });
-      it('shows the first tab when action is show', function () {
+      it('shows the notes tab when action is show', function () {
         this.subject('show');
         expect($('#notes')).toHaveClass('active');
       });
-      it('shows the notes tab when action is notes', function () {
-        this.subject('notes');
-        expect($('#notes')).toHaveClass('active');
-      });
       it('shows the commits tab when action is commits', function () {
         this.subject('commits');
         expect($('#commits')).toHaveClass('active');
@@ -153,7 +157,7 @@ import 'vendor/jquery.scrollTo';
         setLocation({
           pathname: '/foo/bar/merge_requests/1/commits'
         });
-        expect(this.subject('notes')).toBe('/foo/bar/merge_requests/1');
+        expect(this.subject('show')).toBe('/foo/bar/merge_requests/1');
         expect(this.subject('diffs')).toBe('/foo/bar/merge_requests/1/diffs');
       });
 
@@ -162,7 +166,7 @@ import 'vendor/jquery.scrollTo';
           pathname: '/foo/bar/merge_requests/1/diffs'
         });
 
-        expect(this.subject('notes')).toBe('/foo/bar/merge_requests/1');
+        expect(this.subject('show')).toBe('/foo/bar/merge_requests/1');
         expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits');
       });
 
@@ -170,7 +174,7 @@ import 'vendor/jquery.scrollTo';
         setLocation({
           pathname: '/foo/bar/merge_requests/1/diffs.html'
         });
-        expect(this.subject('notes')).toBe('/foo/bar/merge_requests/1');
+        expect(this.subject('show')).toBe('/foo/bar/merge_requests/1');
         expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits');
       });
 
@@ -271,6 +275,19 @@ import 'vendor/jquery.scrollTo';
     });
 
     describe('loadDiff', function () {
+      beforeEach(() => {
+        loadFixtures('merge_requests/diff_comment.html.raw');
+        spyOn(window.gl.utils, 'getPagePath').and.returnValue('merge_requests');
+        window.gl.ImageFile = () => {};
+        window.notes = new Notes('', []);
+        spyOn(window.notes, 'toggleDiffNote').and.callThrough();
+      });
+
+      afterEach(() => {
+        delete window.gl.ImageFile;
+        delete window.notes;
+      });
+
       it('requires an absolute pathname', function () {
         spyOn($, 'ajax').and.callFake(function (options) {
           expect(options.url).toEqual('/foo/bar/merge_requests/1/diffs.json');
@@ -279,43 +296,112 @@ import 'vendor/jquery.scrollTo';
         this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
       });
 
-      describe('with note fragment hash', () => {
+      describe('with inline diff', () => {
+        let noteId;
+        let noteLineNumId;
+
         beforeEach(() => {
-          loadFixtures('merge_requests/diff_comment.html.raw');
-          spyOn(window.gl.utils, 'getPagePath').and.returnValue('merge_requests');
-          window.notes = new Notes('', []);
-          spyOn(window.notes, 'toggleDiffNote').and.callThrough();
-        });
+          const diffsResponse = getJSONFixture(inlineChangesTabJsonFixture);
+
+          const $html = $(diffsResponse.html);
+          noteId = $html.find('.note').attr('id');
+          noteLineNumId = $html
+            .find('.note')
+            .closest('.notes_holder')
+            .prev('.line_holder')
+            .find('a[data-linenumber]')
+            .attr('href')
+            .replace('#', '');
 
-        afterEach(() => {
-          delete window.notes;
+          spyOn($, 'ajax').and.callFake(function (options) {
+            options.success(diffsResponse);
+          });
         });
 
-        it('should expand and scroll to linked fragment hash #note_xxx', function () {
-          const noteId = 'note_1';
-          spyOn(window.gl.utils, 'getLocationHash').and.returnValue(noteId);
-          spyOn($, 'ajax').and.callFake(function (options) {
-            options.success({ html: `<div id="${noteId}">foo</div>` });
+        describe('with note fragment hash', () => {
+          it('should expand and scroll to linked fragment hash #note_xxx', function () {
+            spyOn(window.gl.utils, 'getLocationHash').and.returnValue(noteId);
+            this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
+
+            expect(noteId.length).toBeGreaterThan(0);
+            expect(window.notes.toggleDiffNote).toHaveBeenCalledWith({
+              target: jasmine.any(Object),
+              lineType: 'old',
+              forceShow: true,
+            });
+          });
+
+          it('should gracefully ignore non-existant fragment hash', function () {
+            spyOn(window.gl.utils, 'getLocationHash').and.returnValue('note_something-that-does-not-exist');
+            this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
+
+            expect(window.notes.toggleDiffNote).not.toHaveBeenCalled();
           });
+        });
 
-          this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
+        describe('with line number fragment hash', () => {
+          it('should gracefully ignore line number fragment hash', function () {
+            spyOn(window.gl.utils, 'getLocationHash').and.returnValue(noteLineNumId);
+            this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
 
-          expect(window.notes.toggleDiffNote).toHaveBeenCalledWith({
-            target: jasmine.any(Object),
-            lineType: 'old',
-            forceShow: true,
+            expect(noteLineNumId.length).toBeGreaterThan(0);
+            expect(window.notes.toggleDiffNote).not.toHaveBeenCalled();
           });
         });
+      });
+
+      describe('with parallel diff', () => {
+        let noteId;
+        let noteLineNumId;
+
+        beforeEach(() => {
+          const diffsResponse = getJSONFixture(parallelChangesTabJsonFixture);
+
+          const $html = $(diffsResponse.html);
+          noteId = $html.find('.note').attr('id');
+          noteLineNumId = $html
+            .find('.note')
+            .closest('.notes_holder')
+            .prev('.line_holder')
+            .find('a[data-linenumber]')
+            .attr('href')
+            .replace('#', '');
 
-        it('should gracefully ignore non-existant fragment hash', function () {
-          spyOn(window.gl.utils, 'getLocationHash').and.returnValue('note_something-that-does-not-exist');
           spyOn($, 'ajax').and.callFake(function (options) {
-            options.success({ html: '' });
+            options.success(diffsResponse);
+          });
+        });
+
+        describe('with note fragment hash', () => {
+          it('should expand and scroll to linked fragment hash #note_xxx', function () {
+            spyOn(window.gl.utils, 'getLocationHash').and.returnValue(noteId);
+
+            this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
+
+            expect(noteId.length).toBeGreaterThan(0);
+            expect(window.notes.toggleDiffNote).toHaveBeenCalledWith({
+              target: jasmine.any(Object),
+              lineType: 'new',
+              forceShow: true,
+            });
           });
 
-          this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
+          it('should gracefully ignore non-existant fragment hash', function () {
+            spyOn(window.gl.utils, 'getLocationHash').and.returnValue('note_something-that-does-not-exist');
+            this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
 
-          expect(window.notes.toggleDiffNote).not.toHaveBeenCalled();
+            expect(window.notes.toggleDiffNote).not.toHaveBeenCalled();
+          });
+        });
+
+        describe('with line number fragment hash', () => {
+          it('should gracefully ignore line number fragment hash', function () {
+            spyOn(window.gl.utils, 'getLocationHash').and.returnValue(noteLineNumId);
+            this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
+
+            expect(noteLineNumId.length).toBeGreaterThan(0);
+            expect(window.notes.toggleDiffNote).not.toHaveBeenCalled();
+          });
         });
       });
     });
diff --git a/spec/javascripts/monitoring/deployments_spec.js b/spec/javascripts/monitoring/deployments_spec.js
deleted file mode 100644
index 19bc11d0f2489f1b36f5bbadf29721a9a91806c9..0000000000000000000000000000000000000000
--- a/spec/javascripts/monitoring/deployments_spec.js
+++ /dev/null
@@ -1,133 +0,0 @@
-import d3 from 'd3';
-import PrometheusGraph from '~/monitoring/prometheus_graph';
-import Deployments from '~/monitoring/deployments';
-import { prometheusMockData } from './prometheus_mock_data';
-
-describe('Metrics deployments', () => {
-  const fixtureName = 'environments/metrics/metrics.html.raw';
-  let deployment;
-  let prometheusGraph;
-
-  const graphElement = () => document.querySelector('.prometheus-graph');
-
-  preloadFixtures(fixtureName);
-
-  beforeEach((done) => {
-    // Setup the view
-    loadFixtures(fixtureName);
-
-    d3.selectAll('.prometheus-graph')
-      .append('g')
-      .attr('class', 'graph-container');
-
-    prometheusGraph = new PrometheusGraph();
-    deployment = new Deployments(1000, 500);
-
-    spyOn(prometheusGraph, 'init');
-    spyOn($, 'ajax').and.callFake(() => {
-      const d = $.Deferred();
-      d.resolve({
-        deployments: [{
-          id: 1,
-          created_at: deployment.chartData[10].time,
-          sha: 'testing',
-          tag: false,
-          ref: {
-            name: 'testing',
-          },
-        }, {
-          id: 2,
-          created_at: deployment.chartData[15].time,
-          sha: '',
-          tag: true,
-          ref: {
-            name: 'tag',
-          },
-        }],
-      });
-
-      setTimeout(done);
-
-      return d.promise();
-    });
-
-    prometheusGraph.configureGraph();
-    prometheusGraph.transformData(prometheusMockData.metrics);
-
-    deployment.init(prometheusGraph.graphSpecificProperties.memory_values.data);
-  });
-
-  it('creates line on graph for deploment', () => {
-    expect(
-      graphElement().querySelectorAll('.deployment-line').length,
-    ).toBe(2);
-  });
-
-  it('creates hidden deploy boxes', () => {
-    expect(
-      graphElement().querySelectorAll('.prometheus-graph .js-deploy-info-box').length,
-    ).toBe(2);
-  });
-
-  it('hides the info boxes by default', () => {
-    expect(
-      graphElement().querySelectorAll('.prometheus-graph .js-deploy-info-box.hidden').length,
-    ).toBe(2);
-  });
-
-  it('shows sha short code when tag is false', () => {
-    expect(
-      graphElement().querySelector('.deploy-info-1-cpu_values .js-deploy-info-box').textContent.trim(),
-    ).toContain('testin');
-  });
-
-  it('shows ref name when tag is true', () => {
-    expect(
-      graphElement().querySelector('.deploy-info-2-cpu_values .js-deploy-info-box').textContent.trim(),
-    ).toContain('tag');
-  });
-
-  it('shows info box when moving mouse over line', () => {
-    deployment.mouseOverDeployInfo(deployment.data[0].xPos, 'cpu_values');
-
-    expect(
-      graphElement().querySelectorAll('.prometheus-graph .js-deploy-info-box.hidden').length,
-    ).toBe(1);
-
-    expect(
-      graphElement().querySelector('.deploy-info-1-cpu_values .js-deploy-info-box.hidden'),
-    ).toBeNull();
-  });
-
-  it('hides previously visible info box when moving mouse away', () => {
-    deployment.mouseOverDeployInfo(500, 'cpu_values');
-
-    expect(
-      graphElement().querySelectorAll('.prometheus-graph .js-deploy-info-box.hidden').length,
-    ).toBe(2);
-
-    expect(
-      graphElement().querySelector('.deploy-info-1-cpu_values .js-deploy-info-box.hidden'),
-    ).not.toBeNull();
-  });
-
-  describe('refText', () => {
-    it('returns shortened SHA', () => {
-      expect(
-        Deployments.refText({
-          tag: false,
-          sha: '123456789',
-        }),
-      ).toBe('123456');
-    });
-
-    it('returns tag name', () => {
-      expect(
-        Deployments.refText({
-          tag: true,
-          ref: 'v1.0',
-        }),
-      ).toBe('v1.0');
-    });
-  });
-});
diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js
new file mode 100644
index 0000000000000000000000000000000000000000..56d938e1fbeda763937f1c85ba1707d2864fa63a
--- /dev/null
+++ b/spec/javascripts/monitoring/mock_data.js
@@ -0,0 +1,4229 @@
+/* eslint-disable quote-props, indent, comma-dangle */
+
+const metricsGroupsAPIResponse = {
+  'success': true,
+  'data': [
+    {
+        'group': 'Kubernetes',
+        'priority': 1,
+        'metrics': [
+          {
+            'title': 'Memory usage',
+            'weight': 1,
+            'queries': [
+                {
+                  'query_range': 'avg(container_memory_usage_bytes{%{environment_filter}}) / 2^20',
+                  'y_label': 'Memory',
+                  'unit': 'MiB',
+                  'result': [
+                    {
+                      'metric': {},
+                      'values': [
+                          [
+                              1495700554.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495700614.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495700674.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495700734.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495700794.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495700854.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495700914.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495700974.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495701034.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495701094.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495701154.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495701214.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495701274.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495701334.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495701394.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495701454.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495701514.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495701574.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495701634.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495701694.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495701754.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495701814.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495701874.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495701934.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495701994.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495702054.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495702114.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495702174.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495702234.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495702294.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495702354.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495702414.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495702474.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495702534.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495702594.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495702654.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495702714.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495702774.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495702834.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495702894.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495702954.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495703014.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495703074.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495703134.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495703194.925,
+                              '8.0390625'
+                          ],
+                          [
+                              1495703254.925,
+                              '8.03515625'
+                          ],
+                          [
+                              1495703314.925,
+                              '8.03515625'
+                          ],
+                          [
+                              1495703374.925,
+                              '8.03515625'
+                          ],
+                          [
+                              1495703434.925,
+                              '8.03515625'
+                          ],
+                          [
+                              1495703494.925,
+                              '8.03515625'
+                          ],
+                          [
+                              1495703554.925,
+                              '8.03515625'
+                          ],
+                          [
+                              1495703614.925,
+                              '8.03515625'
+                          ],
+                          [
+                              1495703674.925,
+                              '8.03515625'
+                          ],
+                          [
+                              1495703734.925,
+                              '8.03515625'
+                          ],
+                          [
+                              1495703794.925,
+                              '8.03515625'
+                          ],
+                          [
+                              1495703854.925,
+                              '8.03515625'
+                          ],
+                          [
+                              1495703914.925,
+                              '8.03515625'
+                          ],
+                          [
+                              1495703974.925,
+                              '8.03515625'
+                          ],
+                          [
+                              1495704034.925,
+                              '8.03515625'
+                          ],
+                          [
+                              1495704094.925,
+                              '8.03515625'
+                          ],
+                          [
+                              1495704154.925,
+                              '8.03515625'
+                          ],
+                          [
+                              1495704214.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495704274.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495704334.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495704394.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495704454.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495704514.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495704574.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495704634.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495704694.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495704754.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495704814.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495704874.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495704934.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495704994.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495705054.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495705114.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495705174.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495705234.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495705294.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495705354.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495705414.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495705474.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495705534.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495705594.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495705654.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495705714.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495705774.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495705834.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495705894.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495705954.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495706014.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495706074.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495706134.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495706194.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495706254.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495706314.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495706374.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495706434.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495706494.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495706554.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495706614.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495706674.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495706734.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495706794.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495706854.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495706914.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495706974.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495707034.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495707094.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495707154.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495707214.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495707274.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495707334.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495707394.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495707454.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495707514.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495707574.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495707634.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495707694.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495707754.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495707814.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495707874.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495707934.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495707994.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495708054.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495708114.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495708174.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495708234.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495708294.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495708354.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495708414.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495708474.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495708534.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495708594.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495708654.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495708714.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495708774.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495708834.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495708894.925,
+                              '7.9296875'
+                          ],
+                          [
+                              1495708954.925,
+                              '7.8984375'
+                          ],
+                          [
+                              1495709014.925,
+                              '7.8984375'
+                          ],
+                          [
+                              1495709074.925,
+                              '7.8984375'
+                          ],
+                          [
+                              1495709134.925,
+                              '7.8984375'
+                          ],
+                          [
+                              1495709194.925,
+                              '7.8984375'
+                          ],
+                          [
+                              1495709254.925,
+                              '7.89453125'
+                          ],
+                          [
+                              1495709314.925,
+                              '7.89453125'
+                          ],
+                          [
+                              1495709374.925,
+                              '7.89453125'
+                          ],
+                          [
+                              1495709434.925,
+                              '7.89453125'
+                          ],
+                          [
+                              1495709494.925,
+                              '7.89453125'
+                          ],
+                          [
+                              1495709554.925,
+                              '7.89453125'
+                          ],
+                          [
+                              1495709614.925,
+                              '7.89453125'
+                          ],
+                          [
+                              1495709674.925,
+                              '7.89453125'
+                          ],
+                          [
+                              1495709734.925,
+                              '7.89453125'
+                          ],
+                          [
+                              1495709794.925,
+                              '7.89453125'
+                          ],
+                          [
+                              1495709854.925,
+                              '7.89453125'
+                          ],
+                          [
+                              1495709914.925,
+                              '7.89453125'
+                          ],
+                          [
+                              1495709974.925,
+                              '7.89453125'
+                          ],
+                          [
+                              1495710034.925,
+                              '7.89453125'
+                          ],
+                          [
+                              1495710094.925,
+                              '7.89453125'
+                          ],
+                          [
+                              1495710154.925,
+                              '7.89453125'
+                          ],
+                          [
+                              1495710214.925,
+                              '7.89453125'
+                          ],
+                          [
+                              1495710274.925,
+                              '7.89453125'
+                          ],
+                          [
+                              1495710334.925,
+                              '7.89453125'
+                          ],
+                          [
+                              1495710394.925,
+                              '7.89453125'
+                          ],
+                          [
+                              1495710454.925,
+                              '7.89453125'
+                          ],
+                          [
+                              1495710514.925,
+                              '7.89453125'
+                          ],
+                          [
+                              1495710574.925,
+                              '7.89453125'
+                          ],
+                          [
+                              1495710634.925,
+                              '7.89453125'
+                          ],
+                          [
+                              1495710694.925,
+                              '7.89453125'
+                          ],
+                          [
+                              1495710754.925,
+                              '7.89453125'
+                          ],
+                          [
+                              1495710814.925,
+                              '7.89453125'
+                          ],
+                          [
+                              1495710874.925,
+                              '7.89453125'
+                          ],
+                          [
+                              1495710934.925,
+                              '7.89453125'
+                          ],
+                          [
+                              1495710994.925,
+                              '7.89453125'
+                          ],
+                          [
+                              1495711054.925,
+                              '7.89453125'
+                          ],
+                          [
+                              1495711114.925,
+                              '7.89453125'
+                          ],
+                          [
+                              1495711174.925,
+                              '7.8515625'
+                          ],
+                          [
+                              1495711234.925,
+                              '7.8515625'
+                          ],
+                          [
+                              1495711294.925,
+                              '7.8515625'
+                          ],
+                          [
+                              1495711354.925,
+                              '7.8515625'
+                          ],
+                          [
+                              1495711414.925,
+                              '7.8515625'
+                          ],
+                          [
+                              1495711474.925,
+                              '7.8515625'
+                          ],
+                          [
+                              1495711534.925,
+                              '7.8515625'
+                          ],
+                          [
+                              1495711594.925,
+                              '7.8515625'
+                          ],
+                          [
+                              1495711654.925,
+                              '7.8515625'
+                          ],
+                          [
+                              1495711714.925,
+                              '7.8515625'
+                          ],
+                          [
+                              1495711774.925,
+                              '7.8515625'
+                          ],
+                          [
+                              1495711834.925,
+                              '7.8515625'
+                          ],
+                          [
+                              1495711894.925,
+                              '7.8515625'
+                          ],
+                          [
+                              1495711954.925,
+                              '7.8515625'
+                          ],
+                          [
+                              1495712014.925,
+                              '7.8515625'
+                          ],
+                          [
+                              1495712074.925,
+                              '7.8515625'
+                          ],
+                          [
+                              1495712134.925,
+                              '7.8515625'
+                          ],
+                          [
+                              1495712194.925,
+                              '7.8515625'
+                          ],
+                          [
+                              1495712254.925,
+                              '7.8515625'
+                          ],
+                          [
+                              1495712314.925,
+                              '7.8515625'
+                          ],
+                          [
+                              1495712374.925,
+                              '7.8515625'
+                          ],
+                          [
+                              1495712434.925,
+                              '7.83203125'
+                          ],
+                          [
+                              1495712494.925,
+                              '7.83203125'
+                          ],
+                          [
+                              1495712554.925,
+                              '7.83203125'
+                          ],
+                          [
+                              1495712614.925,
+                              '7.83203125'
+                          ],
+                          [
+                              1495712674.925,
+                              '7.83203125'
+                          ],
+                          [
+                              1495712734.925,
+                              '7.83203125'
+                          ],
+                          [
+                              1495712794.925,
+                              '7.83203125'
+                          ],
+                          [
+                              1495712854.925,
+                              '7.83203125'
+                          ],
+                          [
+                              1495712914.925,
+                              '7.83203125'
+                          ],
+                          [
+                              1495712974.925,
+                              '7.83203125'
+                          ],
+                          [
+                              1495713034.925,
+                              '7.83203125'
+                          ],
+                          [
+                              1495713094.925,
+                              '7.83203125'
+                          ],
+                          [
+                              1495713154.925,
+                              '7.83203125'
+                          ],
+                          [
+                              1495713214.925,
+                              '7.83203125'
+                          ],
+                          [
+                              1495713274.925,
+                              '7.83203125'
+                          ],
+                          [
+                              1495713334.925,
+                              '7.83203125'
+                          ],
+                          [
+                              1495713394.925,
+                              '7.8125'
+                          ],
+                          [
+                              1495713454.925,
+                              '7.8125'
+                          ],
+                          [
+                              1495713514.925,
+                              '7.8125'
+                          ],
+                          [
+                              1495713574.925,
+                              '7.8125'
+                          ],
+                          [
+                              1495713634.925,
+                              '7.8125'
+                          ],
+                          [
+                              1495713694.925,
+                              '7.8125'
+                          ],
+                          [
+                              1495713754.925,
+                              '7.8125'
+                          ],
+                          [
+                              1495713814.925,
+                              '7.8125'
+                          ],
+                          [
+                              1495713874.925,
+                              '7.8125'
+                          ],
+                          [
+                              1495713934.925,
+                              '7.8125'
+                          ],
+                          [
+                              1495713994.925,
+                              '7.8125'
+                          ],
+                          [
+                              1495714054.925,
+                              '7.8125'
+                          ],
+                          [
+                              1495714114.925,
+                              '7.8125'
+                          ],
+                          [
+                              1495714174.925,
+                              '7.8125'
+                          ],
+                          [
+                              1495714234.925,
+                              '7.8125'
+                          ],
+                          [
+                              1495714294.925,
+                              '7.8125'
+                          ],
+                          [
+                              1495714354.925,
+                              '7.80859375'
+                          ],
+                          [
+                              1495714414.925,
+                              '7.80859375'
+                          ],
+                          [
+                              1495714474.925,
+                              '7.80859375'
+                          ],
+                          [
+                              1495714534.925,
+                              '7.80859375'
+                          ],
+                          [
+                              1495714594.925,
+                              '7.80859375'
+                          ],
+                          [
+                              1495714654.925,
+                              '7.80859375'
+                          ],
+                          [
+                              1495714714.925,
+                              '7.80859375'
+                          ],
+                          [
+                              1495714774.925,
+                              '7.80859375'
+                          ],
+                          [
+                              1495714834.925,
+                              '7.80859375'
+                          ],
+                          [
+                              1495714894.925,
+                              '7.80859375'
+                          ],
+                          [
+                              1495714954.925,
+                              '7.80859375'
+                          ],
+                          [
+                              1495715014.925,
+                              '7.80859375'
+                          ],
+                          [
+                              1495715074.925,
+                              '7.80859375'
+                          ],
+                          [
+                              1495715134.925,
+                              '7.80859375'
+                          ],
+                          [
+                              1495715194.925,
+                              '7.80859375'
+                          ],
+                          [
+                              1495715254.925,
+                              '7.80859375'
+                          ],
+                          [
+                              1495715314.925,
+                              '7.80859375'
+                          ],
+                          [
+                              1495715374.925,
+                              '7.80859375'
+                          ],
+                          [
+                              1495715434.925,
+                              '7.80859375'
+                          ],
+                          [
+                              1495715494.925,
+                              '7.80859375'
+                          ],
+                          [
+                              1495715554.925,
+                              '7.80859375'
+                          ],
+                          [
+                              1495715614.925,
+                              '7.80859375'
+                          ],
+                          [
+                              1495715674.925,
+                              '7.80859375'
+                          ],
+                          [
+                              1495715734.925,
+                              '7.80859375'
+                          ],
+                          [
+                              1495715794.925,
+                              '7.80859375'
+                          ],
+                          [
+                              1495715854.925,
+                              '7.80859375'
+                          ],
+                          [
+                              1495715914.925,
+                              '7.80078125'
+                          ],
+                          [
+                              1495715974.925,
+                              '7.80078125'
+                          ],
+                          [
+                              1495716034.925,
+                              '7.80078125'
+                          ],
+                          [
+                              1495716094.925,
+                              '7.80078125'
+                          ],
+                          [
+                              1495716154.925,
+                              '7.80078125'
+                          ],
+                          [
+                              1495716214.925,
+                              '7.796875'
+                          ],
+                          [
+                              1495716274.925,
+                              '7.796875'
+                          ],
+                          [
+                              1495716334.925,
+                              '7.796875'
+                          ],
+                          [
+                              1495716394.925,
+                              '7.796875'
+                          ],
+                          [
+                              1495716454.925,
+                              '7.796875'
+                          ],
+                          [
+                              1495716514.925,
+                              '7.796875'
+                          ],
+                          [
+                              1495716574.925,
+                              '7.796875'
+                          ],
+                          [
+                              1495716634.925,
+                              '7.796875'
+                          ],
+                          [
+                              1495716694.925,
+                              '7.796875'
+                          ],
+                          [
+                              1495716754.925,
+                              '7.796875'
+                          ],
+                          [
+                              1495716814.925,
+                              '7.796875'
+                          ],
+                          [
+                              1495716874.925,
+                              '7.79296875'
+                          ],
+                          [
+                              1495716934.925,
+                              '7.79296875'
+                          ],
+                          [
+                              1495716994.925,
+                              '7.79296875'
+                          ],
+                          [
+                              1495717054.925,
+                              '7.79296875'
+                          ],
+                          [
+                              1495717114.925,
+                              '7.79296875'
+                          ],
+                          [
+                              1495717174.925,
+                              '7.7890625'
+                          ],
+                          [
+                              1495717234.925,
+                              '7.7890625'
+                          ],
+                          [
+                              1495717294.925,
+                              '7.7890625'
+                          ],
+                          [
+                              1495717354.925,
+                              '7.7890625'
+                          ],
+                          [
+                              1495717414.925,
+                              '7.7890625'
+                          ],
+                          [
+                              1495717474.925,
+                              '7.7890625'
+                          ],
+                          [
+                              1495717534.925,
+                              '7.7890625'
+                          ],
+                          [
+                              1495717594.925,
+                              '7.7890625'
+                          ],
+                          [
+                              1495717654.925,
+                              '7.7890625'
+                          ],
+                          [
+                              1495717714.925,
+                              '7.7890625'
+                          ],
+                          [
+                              1495717774.925,
+                              '7.7890625'
+                          ],
+                          [
+                              1495717834.925,
+                              '7.77734375'
+                          ],
+                          [
+                              1495717894.925,
+                              '7.77734375'
+                          ],
+                          [
+                              1495717954.925,
+                              '7.77734375'
+                          ],
+                          [
+                              1495718014.925,
+                              '7.77734375'
+                          ],
+                          [
+                              1495718074.925,
+                              '7.77734375'
+                          ],
+                          [
+                              1495718134.925,
+                              '7.7421875'
+                          ],
+                          [
+                              1495718194.925,
+                              '7.7421875'
+                          ],
+                          [
+                              1495718254.925,
+                              '7.7421875'
+                          ],
+                          [
+                              1495718314.925,
+                              '7.7421875'
+                          ]
+                      ]
+                    }
+                  ]
+              }
+          ]
+        },
+        {
+            'title': 'CPU usage',
+            'weight': 1,
+            'queries': [
+                {
+                    'query_range': 'avg(rate(container_cpu_usage_seconds_total{%{environment_filter}}[2m])) * 100',
+                    'result': [
+                        {
+                            'metric': {},
+                            'values': [
+                                [
+                                    1495700554.925,
+                                    '0.0010794445585559514'
+                                ],
+                                [
+                                    1495700614.925,
+                                    '0.003927214935433527'
+                                ],
+                                [
+                                    1495700674.925,
+                                    '0.0053045219047619975'
+                                ],
+                                [
+                                    1495700734.925,
+                                    '0.0048892095238097155'
+                                ],
+                                [
+                                    1495700794.925,
+                                    '0.005827140952381137'
+                                ],
+                                [
+                                    1495700854.925,
+                                    '0.00569846906219937'
+                                ],
+                                [
+                                    1495700914.925,
+                                    '0.004972616802849382'
+                                ],
+                                [
+                                    1495700974.925,
+                                    '0.005117509523809902'
+                                ],
+                                [
+                                    1495701034.925,
+                                    '0.00512389061919564'
+                                ],
+                                [
+                                    1495701094.925,
+                                    '0.005199100501890691'
+                                ],
+                                [
+                                    1495701154.925,
+                                    '0.005415746394885837'
+                                ],
+                                [
+                                    1495701214.925,
+                                    '0.005607682788146286'
+                                ],
+                                [
+                                    1495701274.925,
+                                    '0.005641300000000118'
+                                ],
+                                [
+                                    1495701334.925,
+                                    '0.0071166279368766495'
+                                ],
+                                [
+                                    1495701394.925,
+                                    '0.0063242138095234044'
+                                ],
+                                [
+                                    1495701454.925,
+                                    '0.005793314698235304'
+                                ],
+                                [
+                                    1495701514.925,
+                                    '0.00703934942237556'
+                                ],
+                                [
+                                    1495701574.925,
+                                    '0.006357007076123191'
+                                ],
+                                [
+                                    1495701634.925,
+                                    '0.003753167300126738'
+                                ],
+                                [
+                                    1495701694.925,
+                                    '0.005018469678430698'
+                                ],
+                                [
+                                    1495701754.925,
+                                    '0.0045217153371887'
+                                ],
+                                [
+                                    1495701814.925,
+                                    '0.006140104285714119'
+                                ],
+                                [
+                                    1495701874.925,
+                                    '0.004818684285714102'
+                                ],
+                                [
+                                    1495701934.925,
+                                    '0.005079509718955242'
+                                ],
+                                [
+                                    1495701994.925,
+                                    '0.005059981142498263'
+                                ],
+                                [
+                                    1495702054.925,
+                                    '0.005269098389538773'
+                                ],
+                                [
+                                    1495702114.925,
+                                    '0.005269954285714175'
+                                ],
+                                [
+                                    1495702174.925,
+                                    '0.014199241435795856'
+                                ],
+                                [
+                                    1495702234.925,
+                                    '0.01511936843111017'
+                                ],
+                                [
+                                    1495702294.925,
+                                    '0.0060933692920682875'
+                                ],
+                                [
+                                    1495702354.925,
+                                    '0.004945682380952493'
+                                ],
+                                [
+                                    1495702414.925,
+                                    '0.005641266666666565'
+                                ],
+                                [
+                                    1495702474.925,
+                                    '0.005223752857142996'
+                                ],
+                                [
+                                    1495702534.925,
+                                    '0.005743098505699831'
+                                ],
+                                [
+                                    1495702594.925,
+                                    '0.00538493380952391'
+                                ],
+                                [
+                                    1495702654.925,
+                                    '0.005507793883751339'
+                                ],
+                                [
+                                    1495702714.925,
+                                    '0.005666705714285466'
+                                ],
+                                [
+                                    1495702774.925,
+                                    '0.006231530000000112'
+                                ],
+                                [
+                                    1495702834.925,
+                                    '0.006570768635394899'
+                                ],
+                                [
+                                    1495702894.925,
+                                    '0.005551146666666895'
+                                ],
+                                [
+                                    1495702954.925,
+                                    '0.005602604737098058'
+                                ],
+                                [
+                                    1495703014.925,
+                                    '0.00613993580402159'
+                                ],
+                                [
+                                    1495703074.925,
+                                    '0.004770258764368832'
+                                ],
+                                [
+                                    1495703134.925,
+                                    '0.005512376671364914'
+                                ],
+                                [
+                                    1495703194.925,
+                                    '0.005254436666666674'
+                                ],
+                                [
+                                    1495703254.925,
+                                    '0.0050109839141320505'
+                                ],
+                                [
+                                    1495703314.925,
+                                    '0.0049478019256960016'
+                                ],
+                                [
+                                    1495703374.925,
+                                    '0.0037666860965123463'
+                                ],
+                                [
+                                    1495703434.925,
+                                    '0.004813526061656314'
+                                ],
+                                [
+                                    1495703494.925,
+                                    '0.005047748095238278'
+                                ],
+                                [
+                                    1495703554.925,
+                                    '0.00386494081008772'
+                                ],
+                                [
+                                    1495703614.925,
+                                    '0.004304037408111405'
+                                ],
+                                [
+                                    1495703674.925,
+                                    '0.004999466661587168'
+                                ],
+                                [
+                                    1495703734.925,
+                                    '0.004689140476190834'
+                                ],
+                                [
+                                    1495703794.925,
+                                    '0.004746126153582475'
+                                ],
+                                [
+                                    1495703854.925,
+                                    '0.004482706382572302'
+                                ],
+                                [
+                                    1495703914.925,
+                                    '0.004032808931864524'
+                                ],
+                                [
+                                    1495703974.925,
+                                    '0.005728319047618988'
+                                ],
+                                [
+                                    1495704034.925,
+                                    '0.004436139179627006'
+                                ],
+                                [
+                                    1495704094.925,
+                                    '0.004553455714285617'
+                                ],
+                                [
+                                    1495704154.925,
+                                    '0.003455244285714341'
+                                ],
+                                [
+                                    1495704214.925,
+                                    '0.004742244761904621'
+                                ],
+                                [
+                                    1495704274.925,
+                                    '0.005366978571428422'
+                                ],
+                                [
+                                    1495704334.925,
+                                    '0.004257954837665058'
+                                ],
+                                [
+                                    1495704394.925,
+                                    '0.005431603259831257'
+                                ],
+                                [
+                                    1495704454.925,
+                                    '0.0052009214498621986'
+                                ],
+                                [
+                                    1495704514.925,
+                                    '0.004317201904761618'
+                                ],
+                                [
+                                    1495704574.925,
+                                    '0.004307384285714157'
+                                ],
+                                [
+                                    1495704634.925,
+                                    '0.004789801146644822'
+                                ],
+                                [
+                                    1495704694.925,
+                                    '0.0051429795906706485'
+                                ],
+                                [
+                                    1495704754.925,
+                                    '0.005322495714285479'
+                                ],
+                                [
+                                    1495704814.925,
+                                    '0.004512809333244233'
+                                ],
+                                [
+                                    1495704874.925,
+                                    '0.004953843582568726'
+                                ],
+                                [
+                                    1495704934.925,
+                                    '0.005812690120858119'
+                                ],
+                                [
+                                    1495704994.925,
+                                    '0.004997024285714838'
+                                ],
+                                [
+                                    1495705054.925,
+                                    '0.005246216154439592'
+                                ],
+                                [
+                                    1495705114.925,
+                                    '0.0063494966618726795'
+                                ],
+                                [
+                                    1495705174.925,
+                                    '0.005306004342898225'
+                                ],
+                                [
+                                    1495705234.925,
+                                    '0.005081412857142978'
+                                ],
+                                [
+                                    1495705294.925,
+                                    '0.00511409523809522'
+                                ],
+                                [
+                                    1495705354.925,
+                                    '0.0047861001481192'
+                                ],
+                                [
+                                    1495705414.925,
+                                    '0.005107688228042962'
+                                ],
+                                [
+                                    1495705474.925,
+                                    '0.005271929582294012'
+                                ],
+                                [
+                                    1495705534.925,
+                                    '0.004453254502681249'
+                                ],
+                                [
+                                    1495705594.925,
+                                    '0.005799134293959226'
+                                ],
+                                [
+                                    1495705654.925,
+                                    '0.005340865929502478'
+                                ],
+                                [
+                                    1495705714.925,
+                                    '0.004911654761904942'
+                                ],
+                                [
+                                    1495705774.925,
+                                    '0.005888234873953261'
+                                ],
+                                [
+                                    1495705834.925,
+                                    '0.005565283333332954'
+                                ],
+                                [
+                                    1495705894.925,
+                                    '0.005522869047618869'
+                                ],
+                                [
+                                    1495705954.925,
+                                    '0.005177549737621646'
+                                ],
+                                [
+                                    1495706014.925,
+                                    '0.0053145810232096465'
+                                ],
+                                [
+                                    1495706074.925,
+                                    '0.004751095238095275'
+                                ],
+                                [
+                                    1495706134.925,
+                                    '0.006242077142856976'
+                                ],
+                                [
+                                    1495706194.925,
+                                    '0.00621034406957871'
+                                ],
+                                [
+                                    1495706254.925,
+                                    '0.006887592738978596'
+                                ],
+                                [
+                                    1495706314.925,
+                                    '0.006328128779726213'
+                                ],
+                                [
+                                    1495706374.925,
+                                    '0.007488363809523927'
+                                ],
+                                [
+                                    1495706434.925,
+                                    '0.006193758571428157'
+                                ],
+                                [
+                                    1495706494.925,
+                                    '0.0068798371839706935'
+                                ],
+                                [
+                                    1495706554.925,
+                                    '0.005757034340423128'
+                                ],
+                                [
+                                    1495706614.925,
+                                    '0.004571388497294698'
+                                ],
+                                [
+                                    1495706674.925,
+                                    '0.00620283044923395'
+                                ],
+                                [
+                                    1495706734.925,
+                                    '0.005607562380952455'
+                                ],
+                                [
+                                    1495706794.925,
+                                    '0.005506969933620308'
+                                ],
+                                [
+                                    1495706854.925,
+                                    '0.005621118095238131'
+                                ],
+                                [
+                                    1495706914.925,
+                                    '0.004876606098698849'
+                                ],
+                                [
+                                    1495706974.925,
+                                    '0.0047871205988517206'
+                                ],
+                                [
+                                    1495707034.925,
+                                    '0.00526405939458784'
+                                ],
+                                [
+                                    1495707094.925,
+                                    '0.005716323800605852'
+                                ],
+                                [
+                                    1495707154.925,
+                                    '0.005301459523809575'
+                                ],
+                                [
+                                    1495707214.925,
+                                    '0.0051613042857144905'
+                                ],
+                                [
+                                    1495707274.925,
+                                    '0.005384792857142714'
+                                ],
+                                [
+                                    1495707334.925,
+                                    '0.005259719047619222'
+                                ],
+                                [
+                                    1495707394.925,
+                                    '0.00584101142857182'
+                                ],
+                                [
+                                    1495707454.925,
+                                    '0.0060066121920326326'
+                                ],
+                                [
+                                    1495707514.925,
+                                    '0.006359978571428453'
+                                ],
+                                [
+                                    1495707574.925,
+                                    '0.006315876322151109'
+                                ],
+                                [
+                                    1495707634.925,
+                                    '0.005590012517198831'
+                                ],
+                                [
+                                    1495707694.925,
+                                    '0.005517419877137072'
+                                ],
+                                [
+                                    1495707754.925,
+                                    '0.006089813430348506'
+                                ],
+                                [
+                                    1495707814.925,
+                                    '0.00466754476190479'
+                                ],
+                                [
+                                    1495707874.925,
+                                    '0.006059954380517721'
+                                ],
+                                [
+                                    1495707934.925,
+                                    '0.005085657142856972'
+                                ],
+                                [
+                                    1495707994.925,
+                                    '0.005897665238095296'
+                                ],
+                                [
+                                    1495708054.925,
+                                    '0.0062282023199555885'
+                                ],
+                                [
+                                    1495708114.925,
+                                    '0.00526214553236979'
+                                ],
+                                [
+                                    1495708174.925,
+                                    '0.0044803300000000644'
+                                ],
+                                [
+                                    1495708234.925,
+                                    '0.005421443333333592'
+                                ],
+                                [
+                                    1495708294.925,
+                                    '0.005694326244512144'
+                                ],
+                                [
+                                    1495708354.925,
+                                    '0.005527721904761457'
+                                ],
+                                [
+                                    1495708414.925,
+                                    '0.005988819523809819'
+                                ],
+                                [
+                                    1495708474.925,
+                                    '0.005484704285714448'
+                                ],
+                                [
+                                    1495708534.925,
+                                    '0.005041123649230085'
+                                ],
+                                [
+                                    1495708594.925,
+                                    '0.005717767639612059'
+                                ],
+                                [
+                                    1495708654.925,
+                                    '0.005412954417342863'
+                                ],
+                                [
+                                    1495708714.925,
+                                    '0.005833343333333254'
+                                ],
+                                [
+                                    1495708774.925,
+                                    '0.005448135238094969'
+                                ],
+                                [
+                                    1495708834.925,
+                                    '0.005117341428571432'
+                                ],
+                                [
+                                    1495708894.925,
+                                    '0.005888345825277833'
+                                ],
+                                [
+                                    1495708954.925,
+                                    '0.005398543809524135'
+                                ],
+                                [
+                                    1495709014.925,
+                                    '0.005325611428571416'
+                                ],
+                                [
+                                    1495709074.925,
+                                    '0.005848668571428527'
+                                ],
+                                [
+                                    1495709134.925,
+                                    '0.005135003105145044'
+                                ],
+                                [
+                                    1495709194.925,
+                                    '0.0054551400000003'
+                                ],
+                                [
+                                    1495709254.925,
+                                    '0.005319472937322171'
+                                ],
+                                [
+                                    1495709314.925,
+                                    '0.00585677857142792'
+                                ],
+                                [
+                                    1495709374.925,
+                                    '0.0062146261904759215'
+                                ],
+                                [
+                                    1495709434.925,
+                                    '0.0067105060904182265'
+                                ],
+                                [
+                                    1495709494.925,
+                                    '0.005829691904762108'
+                                ],
+                                [
+                                    1495709554.925,
+                                    '0.005719280952381261'
+                                ],
+                                [
+                                    1495709614.925,
+                                    '0.005682603793416407'
+                                ],
+                                [
+                                    1495709674.925,
+                                    '0.0055272846277326934'
+                                ],
+                                [
+                                    1495709734.925,
+                                    '0.0057123680952386735'
+                                ],
+                                [
+                                    1495709794.925,
+                                    '0.00520597958075818'
+                                ],
+                                [
+                                    1495709854.925,
+                                    '0.005584358957263837'
+                                ],
+                                [
+                                    1495709914.925,
+                                    '0.005601104275197466'
+                                ],
+                                [
+                                    1495709974.925,
+                                    '0.005991657142857066'
+                                ],
+                                [
+                                    1495710034.925,
+                                    '0.00553722238095218'
+                                ],
+                                [
+                                    1495710094.925,
+                                    '0.005127883122696293'
+                                ],
+                                [
+                                    1495710154.925,
+                                    '0.005498111927534584'
+                                ],
+                                [
+                                    1495710214.925,
+                                    '0.005609934069084202'
+                                ],
+                                [
+                                    1495710274.925,
+                                    '0.00459206285714307'
+                                ],
+                                [
+                                    1495710334.925,
+                                    '0.0047910828571428084'
+                                ],
+                                [
+                                    1495710394.925,
+                                    '0.0056014671288845685'
+                                ],
+                                [
+                                    1495710454.925,
+                                    '0.005686936791078528'
+                                ],
+                                [
+                                    1495710514.925,
+                                    '0.00444480476190448'
+                                ],
+                                [
+                                    1495710574.925,
+                                    '0.005780394696738921'
+                                ],
+                                [
+                                    1495710634.925,
+                                    '0.0053107227550210365'
+                                ],
+                                [
+                                    1495710694.925,
+                                    '0.005096031495761817'
+                                ],
+                                [
+                                    1495710754.925,
+                                    '0.005451377979091524'
+                                ],
+                                [
+                                    1495710814.925,
+                                    '0.005328136666667083'
+                                ],
+                                [
+                                    1495710874.925,
+                                    '0.006020612857143043'
+                                ],
+                                [
+                                    1495710934.925,
+                                    '0.0061063585714285365'
+                                ],
+                                [
+                                    1495710994.925,
+                                    '0.006018346015752312'
+                                ],
+                                [
+                                    1495711054.925,
+                                    '0.005069130952381193'
+                                ],
+                                [
+                                    1495711114.925,
+                                    '0.005458406190476052'
+                                ],
+                                [
+                                    1495711174.925,
+                                    '0.00577219190476179'
+                                ],
+                                [
+                                    1495711234.925,
+                                    '0.005760814645658314'
+                                ],
+                                [
+                                    1495711294.925,
+                                    '0.005371875716579101'
+                                ],
+                                [
+                                    1495711354.925,
+                                    '0.0064232666666665834'
+                                ],
+                                [
+                                    1495711414.925,
+                                    '0.009369806836906667'
+                                ],
+                                [
+                                    1495711474.925,
+                                    '0.008956864761904692'
+                                ],
+                                [
+                                    1495711534.925,
+                                    '0.005266849368559271'
+                                ],
+                                [
+                                    1495711594.925,
+                                    '0.005335111364934262'
+                                ],
+                                [
+                                    1495711654.925,
+                                    '0.006461778319586945'
+                                ],
+                                [
+                                    1495711714.925,
+                                    '0.004687939890762393'
+                                ],
+                                [
+                                    1495711774.925,
+                                    '0.004438831245760684'
+                                ],
+                                [
+                                    1495711834.925,
+                                    '0.005142786666666613'
+                                ],
+                                [
+                                    1495711894.925,
+                                    '0.007257734212054963'
+                                ],
+                                [
+                                    1495711954.925,
+                                    '0.005621991904761494'
+                                ],
+                                [
+                                    1495712014.925,
+                                    '0.007868689999999862'
+                                ],
+                                [
+                                    1495712074.925,
+                                    '0.00910970215275738'
+                                ],
+                                [
+                                    1495712134.925,
+                                    '0.006151004285714278'
+                                ],
+                                [
+                                    1495712194.925,
+                                    '0.005447120924961522'
+                                ],
+                                [
+                                    1495712254.925,
+                                    '0.005150705153929503'
+                                ],
+                                [
+                                    1495712314.925,
+                                    '0.006358108714969314'
+                                ],
+                                [
+                                    1495712374.925,
+                                    '0.0057725354795696475'
+                                ],
+                                [
+                                    1495712434.925,
+                                    '0.005232139047619015'
+                                ],
+                                [
+                                    1495712494.925,
+                                    '0.004932809617949037'
+                                ],
+                                [
+                                    1495712554.925,
+                                    '0.004511607508499662'
+                                ],
+                                [
+                                    1495712614.925,
+                                    '0.00440487701522666'
+                                ],
+                                [
+                                    1495712674.925,
+                                    '0.005479113333333174'
+                                ],
+                                [
+                                    1495712734.925,
+                                    '0.004726317619047547'
+                                ],
+                                [
+                                    1495712794.925,
+                                    '0.005582041102958029'
+                                ],
+                                [
+                                    1495712854.925,
+                                    '0.006381481216082099'
+                                ],
+                                [
+                                    1495712914.925,
+                                    '0.005474260014095208'
+                                ],
+                                [
+                                    1495712974.925,
+                                    '0.00567597142857188'
+                                ],
+                                [
+                                    1495713034.925,
+                                    '0.0064741233333332985'
+                                ],
+                                [
+                                    1495713094.925,
+                                    '0.005467475714285271'
+                                ],
+                                [
+                                    1495713154.925,
+                                    '0.004868648393824457'
+                                ],
+                                [
+                                    1495713214.925,
+                                    '0.005254923286444893'
+                                ],
+                                [
+                                    1495713274.925,
+                                    '0.005599217150312865'
+                                ],
+                                [
+                                    1495713334.925,
+                                    '0.005105413720618919'
+                                ],
+                                [
+                                    1495713394.925,
+                                    '0.007246073333333279'
+                                ],
+                                [
+                                    1495713454.925,
+                                    '0.005990312380952272'
+                                ],
+                                [
+                                    1495713514.925,
+                                    '0.005594601853351101'
+                                ],
+                                [
+                                    1495713574.925,
+                                    '0.004739258673727054'
+                                ],
+                                [
+                                    1495713634.925,
+                                    '0.003932121428571783'
+                                ],
+                                [
+                                    1495713694.925,
+                                    '0.005018188268459395'
+                                ],
+                                [
+                                    1495713754.925,
+                                    '0.004538238095237985'
+                                ],
+                                [
+                                    1495713814.925,
+                                    '0.00561816643265435'
+                                ],
+                                [
+                                    1495713874.925,
+                                    '0.0063132584495033586'
+                                ],
+                                [
+                                    1495713934.925,
+                                    '0.00442385238095213'
+                                ],
+                                [
+                                    1495713994.925,
+                                    '0.004181795887658453'
+                                ],
+                                [
+                                    1495714054.925,
+                                    '0.004437759047619037'
+                                ],
+                                [
+                                    1495714114.925,
+                                    '0.006421748157178241'
+                                ],
+                                [
+                                    1495714174.925,
+                                    '0.006525143809523842'
+                                ],
+                                [
+                                    1495714234.925,
+                                    '0.004715904935144247'
+                                ],
+                                [
+                                    1495714294.925,
+                                    '0.005966040152763461'
+                                ],
+                                [
+                                    1495714354.925,
+                                    '0.005614535466921674'
+                                ],
+                                [
+                                    1495714414.925,
+                                    '0.004934375119415906'
+                                ],
+                                [
+                                    1495714474.925,
+                                    '0.0054122933333327385'
+                                ],
+                                [
+                                    1495714534.925,
+                                    '0.004926540699612279'
+                                ],
+                                [
+                                    1495714594.925,
+                                    '0.006124649517134237'
+                                ],
+                                [
+                                    1495714654.925,
+                                    '0.004629427092013995'
+                                ],
+                                [
+                                    1495714714.925,
+                                    '0.005117951257607005'
+                                ],
+                                [
+                                    1495714774.925,
+                                    '0.004868774512685422'
+                                ],
+                                [
+                                    1495714834.925,
+                                    '0.005310093333333399'
+                                ],
+                                [
+                                    1495714894.925,
+                                    '0.0054907752286127345'
+                                ],
+                                [
+                                    1495714954.925,
+                                    '0.004597678117351089'
+                                ],
+                                [
+                                    1495715014.925,
+                                    '0.0059622552380952'
+                                ],
+                                [
+                                    1495715074.925,
+                                    '0.005352457072655368'
+                                ],
+                                [
+                                    1495715134.925,
+                                    '0.005491630952381143'
+                                ],
+                                [
+                                    1495715194.925,
+                                    '0.006391770078379791'
+                                ],
+                                [
+                                    1495715254.925,
+                                    '0.005933472857142518'
+                                ],
+                                [
+                                    1495715314.925,
+                                    '0.005301314285714163'
+                                ],
+                                [
+                                    1495715374.925,
+                                    '0.0058352959724814165'
+                                ],
+                                [
+                                    1495715434.925,
+                                    '0.006154755147867044'
+                                ],
+                                [
+                                    1495715494.925,
+                                    '0.009391935637482038'
+                                ],
+                                [
+                                    1495715554.925,
+                                    '0.007846462857142592'
+                                ],
+                                [
+                                    1495715614.925,
+                                    '0.00477608215316353'
+                                ],
+                                [
+                                    1495715674.925,
+                                    '0.006132865238094998'
+                                ],
+                                [
+                                    1495715734.925,
+                                    '0.006159762457649516'
+                                ],
+                                [
+                                    1495715794.925,
+                                    '0.005957307073265968'
+                                ],
+                                [
+                                    1495715854.925,
+                                    '0.006652319091792501'
+                                ],
+                                [
+                                    1495715914.925,
+                                    '0.005493557402895287'
+                                ],
+                                [
+                                    1495715974.925,
+                                    '0.0058652434829145166'
+                                ],
+                                [
+                                    1495716034.925,
+                                    '0.005627400430468021'
+                                ],
+                                [
+                                    1495716094.925,
+                                    '0.006240656190475609'
+                                ],
+                                [
+                                    1495716154.925,
+                                    '0.006305997676168624'
+                                ],
+                                [
+                                    1495716214.925,
+                                    '0.005388057732783248'
+                                ],
+                                [
+                                    1495716274.925,
+                                    '0.0052814916048421244'
+                                ],
+                                [
+                                    1495716334.925,
+                                    '0.00699498614272497'
+                                ],
+                                [
+                                    1495716394.925,
+                                    '0.00627768693035141'
+                                ],
+                                [
+                                    1495716454.925,
+                                    '0.0042411487048161145'
+                                ],
+                                [
+                                    1495716514.925,
+                                    '0.005348647473627653'
+                                ],
+                                [
+                                    1495716574.925,
+                                    '0.0047176657142853975'
+                                ],
+                                [
+                                    1495716634.925,
+                                    '0.004437898571428686'
+                                ],
+                                [
+                                    1495716694.925,
+                                    '0.004923527366927261'
+                                ],
+                                [
+                                    1495716754.925,
+                                    '0.005131935066048421'
+                                ],
+                                [
+                                    1495716814.925,
+                                    '0.005046949523809611'
+                                ],
+                                [
+                                    1495716874.925,
+                                    '0.00547184095238092'
+                                ],
+                                [
+                                    1495716934.925,
+                                    '0.005224140016380444'
+                                ],
+                                [
+                                    1495716994.925,
+                                    '0.005297991171665292'
+                                ],
+                                [
+                                    1495717054.925,
+                                    '0.005492965995623498'
+                                ],
+                                [
+                                    1495717114.925,
+                                    '0.005754660000000403'
+                                ],
+                                [
+                                    1495717174.925,
+                                    '0.005949557138639285'
+                                ],
+                                [
+                                    1495717234.925,
+                                    '0.006091816112534666'
+                                ],
+                                [
+                                    1495717294.925,
+                                    '0.005554210080192063'
+                                ],
+                                [
+                                    1495717354.925,
+                                    '0.006411504395279871'
+                                ],
+                                [
+                                    1495717414.925,
+                                    '0.006319643996609606'
+                                ],
+                                [
+                                    1495717474.925,
+                                    '0.005539174405717675'
+                                ],
+                                [
+                                    1495717534.925,
+                                    '0.0053157078842772255'
+                                ],
+                                [
+                                    1495717594.925,
+                                    '0.005247480952381066'
+                                ],
+                                [
+                                    1495717654.925,
+                                    '0.004820141620396252'
+                                ],
+                                [
+                                    1495717714.925,
+                                    '0.005906173868322844'
+                                ],
+                                [
+                                    1495717774.925,
+                                    '0.006173117219570961'
+                                ],
+                                [
+                                    1495717834.925,
+                                    '0.005963340952380661'
+                                ],
+                                [
+                                    1495717894.925,
+                                    '0.005698976627681527'
+                                ],
+                                [
+                                    1495717954.925,
+                                    '0.004751279096346378'
+                                ],
+                                [
+                                    1495718014.925,
+                                    '0.005733142379359711'
+                                ],
+                                [
+                                    1495718074.925,
+                                    '0.004831689010348035'
+                                ],
+                                [
+                                    1495718134.925,
+                                    '0.005188370476191092'
+                                ],
+                                [
+                                    1495718194.925,
+                                    '0.004793227554547938'
+                                ],
+                                [
+                                    1495718254.925,
+                                    '0.003997442857142731'
+                                ],
+                                [
+                                    1495718314.925,
+                                    '0.004386040132951264'
+                                ]
+                            ]
+                        }
+                    ]
+                }
+            ]
+        }
+      ]
+    }
+  ],
+  'last_update': '2017-05-25T13:18:34.949Z'
+};
+
+export default metricsGroupsAPIResponse;
+
+const responseMockData = {
+  'GET': {
+    '/root/hello-prometheus/environments/30/additional_metrics.json': metricsGroupsAPIResponse,
+    'http://test.host/frontend-fixtures/environments-project/environments/1/additional_metrics.json': metricsGroupsAPIResponse, // TODO: MAke sure this works in the monitoring_bundle_spec
+  },
+};
+
+export const deploymentData = [
+  {
+    id: 111,
+    iid: 3,
+    sha: 'f5bcd1d9dac6fa4137e2510b9ccd134ef2e84187',
+    ref: {
+      name: 'master'
+    },
+    created_at: '2017-05-31T21:23:37.881Z',
+    tag: false,
+    'last?': true
+  },
+  {
+    id: 110,
+    iid: 2,
+    sha: 'f5bcd1d9dac6fa4137e2510b9ccd134ef2e84187',
+    ref: {
+      name: 'master'
+    },
+    created_at: '2017-05-30T20:08:04.629Z',
+    tag: false,
+    'last?': false
+  },
+  {
+    id: 109,
+    iid: 1,
+    sha: '6511e58faafaa7ad2228990ec57f19d66f7db7c2',
+    ref: {
+      name: 'update2-readme'
+    },
+    created_at: '2017-05-30T17:42:38.409Z',
+    tag: false,
+    'last?': false
+  }
+];
+
+export const statePaths = {
+  settingsPath: '/root/hello-prometheus/services/prometheus/edit',
+  documentationPath: '/help/administration/monitoring/prometheus/index.md',
+};
+
+export const singleRowMetrics = [
+  {
+    'title': 'CPU usage',
+    'weight': 1,
+    'y_label': 'Memory',
+    'queries': [
+      {
+        'query_range': 'avg(rate(container_cpu_usage_seconds_total{%{environment_filter}}[2m])) * 100',
+        'result': [
+          {
+            'metric': {
+
+            },
+            'values': [
+              {
+                'time': '2017-06-04T21:22:59.508Z',
+                'value': '0.06335544298150002'
+              },
+              {
+                'time': '2017-06-04T21:23:59.508Z',
+                'value': '0.0420347312480917'
+              },
+              {
+                'time': '2017-06-04T21:24:59.508Z',
+                'value': '0.0023175131665412706'
+              },
+              {
+                'time': '2017-06-04T21:25:59.508Z',
+                'value': '0.002315870476190476'
+              },
+              {
+                'time': '2017-06-04T21:26:59.508Z',
+                'value': '0.0025005961904761894'
+              },
+              {
+                'time': '2017-06-04T21:27:59.508Z',
+                'value': '0.0024612605834341264'
+              },
+              {
+                'time': '2017-06-04T21:28:59.508Z',
+                'value': '0.002313129398767631'
+              },
+              {
+                'time': '2017-06-04T21:29:59.508Z',
+                'value': '0.002411067353663882'
+              },
+              {
+                'time': '2017-06-04T21:30:59.508Z',
+                'value': '0.002577309263721303'
+              },
+              {
+                'time': '2017-06-04T21:31:59.508Z',
+                'value': '0.00242688307730403'
+              },
+              {
+                'time': '2017-06-04T21:32:59.508Z',
+                'value': '0.0024168360301330457'
+              },
+              {
+                'time': '2017-06-04T21:33:59.508Z',
+                'value': '0.0020449528090743714'
+              },
+              {
+                'time': '2017-06-04T21:34:59.508Z',
+                'value': '0.0019149619047619036'
+              },
+              {
+                'time': '2017-06-04T21:35:59.508Z',
+                'value': '0.0024491714364625094'
+              },
+              {
+                'time': '2017-06-04T21:36:59.508Z',
+                'value': '0.002728773131172677'
+              },
+              {
+                'time': '2017-06-04T21:37:59.508Z',
+                'value': '0.0028439119047618997'
+              },
+              {
+                'time': '2017-06-04T21:38:59.508Z',
+                'value': '0.0026307480952380917'
+              },
+              {
+                'time': '2017-06-04T21:39:59.508Z',
+                'value': '0.0025024842620546446'
+              },
+              {
+                'time': '2017-06-04T21:40:59.508Z',
+                'value': '0.002300662387260825'
+              },
+              {
+                'time': '2017-06-04T21:41:59.508Z',
+                'value': '0.002052890924848337'
+              },
+              {
+                'time': '2017-06-04T21:42:59.508Z',
+                'value': '0.0023711195238095275'
+              },
+              {
+                'time': '2017-06-04T21:43:59.508Z',
+                'value': '0.002513477619047618'
+              },
+              {
+                'time': '2017-06-04T21:44:59.508Z',
+                'value': '0.0023489776287844897'
+              },
+              {
+                'time': '2017-06-04T21:45:59.508Z',
+                'value': '0.002542572310212481'
+              },
+              {
+                'time': '2017-06-04T21:46:59.508Z',
+                'value': '0.0024579470671707952'
+              },
+              {
+                'time': '2017-06-04T21:47:59.508Z',
+                'value': '0.0028725150236664403'
+              },
+              {
+                'time': '2017-06-04T21:48:59.508Z',
+                'value': '0.0024356089105610525'
+              },
+              {
+                'time': '2017-06-04T21:49:59.508Z',
+                'value': '0.002544015828269929'
+              },
+              {
+                'time': '2017-06-04T21:50:59.508Z',
+                'value': '0.0029595013380824906'
+              },
+              {
+                'time': '2017-06-04T21:51:59.508Z',
+                'value': '0.0023084015085858'
+              },
+              {
+                'time': '2017-06-04T21:52:59.508Z',
+                'value': '0.0021070500000000083'
+              },
+              {
+                'time': '2017-06-04T21:53:59.508Z',
+                'value': '0.0022950066191106617'
+              },
+              {
+                'time': '2017-06-04T21:54:59.508Z',
+                'value': '0.002492719454470995'
+              },
+              {
+                'time': '2017-06-04T21:55:59.508Z',
+                'value': '0.00244312761904762'
+              },
+              {
+                'time': '2017-06-04T21:56:59.508Z',
+                'value': '0.0023495500000000028'
+              },
+              {
+                'time': '2017-06-04T21:57:59.508Z',
+                'value': '0.0020597072353070005'
+              },
+              {
+                'time': '2017-06-04T21:58:59.508Z',
+                'value': '0.0021482352044800866'
+              },
+              {
+                'time': '2017-06-04T21:59:59.508Z',
+                'value': '0.002333490000000004'
+              },
+              {
+                'time': '2017-06-04T22:00:59.508Z',
+                'value': '0.0025899442857142815'
+              },
+              {
+                'time': '2017-06-04T22:01:59.508Z',
+                'value': '0.002430299999999999'
+              },
+              {
+                'time': '2017-06-04T22:02:59.508Z',
+                'value': '0.0023550328092113476'
+              },
+              {
+                'time': '2017-06-04T22:03:59.508Z',
+                'value': '0.0026521871636872793'
+              },
+              {
+                'time': '2017-06-04T22:04:59.508Z',
+                'value': '0.0023080671428571398'
+              },
+              {
+                'time': '2017-06-04T22:05:59.508Z',
+                'value': '0.0024108401032390896'
+              },
+              {
+                'time': '2017-06-04T22:06:59.508Z',
+                'value': '0.002433249366678738'
+              },
+              {
+                'time': '2017-06-04T22:07:59.508Z',
+                'value': '0.0023242202306688682'
+              },
+              {
+                'time': '2017-06-04T22:08:59.508Z',
+                'value': '0.002388222857142859'
+              },
+              {
+                'time': '2017-06-04T22:09:59.508Z',
+                'value': '0.002115974914046794'
+              },
+              {
+                'time': '2017-06-04T22:10:59.508Z',
+                'value': '0.0025090043331269917'
+              },
+              {
+                'time': '2017-06-04T22:11:59.508Z',
+                'value': '0.002445507057277277'
+              },
+              {
+                'time': '2017-06-04T22:12:59.508Z',
+                'value': '0.0026348773751130976'
+              },
+              {
+                'time': '2017-06-04T22:13:59.508Z',
+                'value': '0.0025616258583088104'
+              },
+              {
+                'time': '2017-06-04T22:14:59.508Z',
+                'value': '0.0021544093415751505'
+              },
+              {
+                'time': '2017-06-04T22:15:59.508Z',
+                'value': '0.002649394767668881'
+              },
+              {
+                'time': '2017-06-04T22:16:59.508Z',
+                'value': '0.0024023332666685705'
+              },
+              {
+                'time': '2017-06-04T22:17:59.508Z',
+                'value': '0.0025444105294235306'
+              },
+              {
+                'time': '2017-06-04T22:18:59.508Z',
+                'value': '0.0027298872305772806'
+              },
+              {
+                'time': '2017-06-04T22:19:59.508Z',
+                'value': '0.0022880104956379287'
+              },
+              {
+                'time': '2017-06-04T22:20:59.508Z',
+                'value': '0.002473246666666661'
+              },
+              {
+                'time': '2017-06-04T22:21:59.508Z',
+                'value': '0.002259948381935587'
+              },
+              {
+                'time': '2017-06-04T22:22:59.508Z',
+                'value': '0.0025778470886268835'
+              },
+              {
+                'time': '2017-06-04T22:23:59.508Z',
+                'value': '0.002246127910852894'
+              },
+              {
+                'time': '2017-06-04T22:24:59.508Z',
+                'value': '0.0020697466666666758'
+              },
+              {
+                'time': '2017-06-04T22:25:59.508Z',
+                'value': '0.00225859722473547'
+              },
+              {
+                'time': '2017-06-04T22:26:59.508Z',
+                'value': '0.0026466728254554814'
+              },
+              {
+                'time': '2017-06-04T22:27:59.508Z',
+                'value': '0.002151247619047619'
+              },
+              {
+                'time': '2017-06-04T22:28:59.508Z',
+                'value': '0.002324161444543914'
+              },
+              {
+                'time': '2017-06-04T22:29:59.508Z',
+                'value': '0.002476474313796452'
+              },
+              {
+                'time': '2017-06-04T22:30:59.508Z',
+                'value': '0.0023922184232080517'
+              },
+              {
+                'time': '2017-06-04T22:31:59.508Z',
+                'value': '0.0025094934237468933'
+              },
+              {
+                'time': '2017-06-04T22:32:59.508Z',
+                'value': '0.0025665311098200883'
+              },
+              {
+                'time': '2017-06-04T22:33:59.508Z',
+                'value': '0.0024154900681661374'
+              },
+              {
+                'time': '2017-06-04T22:34:59.508Z',
+                'value': '0.0023267450166192037'
+              },
+              {
+                'time': '2017-06-04T22:35:59.508Z',
+                'value': '0.002156521904761904'
+              },
+              {
+                'time': '2017-06-04T22:36:59.508Z',
+                'value': '0.0025474356898637007'
+              },
+              {
+                'time': '2017-06-04T22:37:59.508Z',
+                'value': '0.0025989409624670233'
+              },
+              {
+                'time': '2017-06-04T22:38:59.508Z',
+                'value': '0.002348336664762987'
+              },
+              {
+                'time': '2017-06-04T22:39:59.508Z',
+                'value': '0.002665888246554726'
+              },
+              {
+                'time': '2017-06-04T22:40:59.508Z',
+                'value': '0.002652684787474174'
+              },
+              {
+                'time': '2017-06-04T22:41:59.508Z',
+                'value': '0.002472620430865355'
+              },
+              {
+                'time': '2017-06-04T22:42:59.508Z',
+                'value': '0.0020616469210110247'
+              },
+              {
+                'time': '2017-06-04T22:43:59.508Z',
+                'value': '0.0022434546372311934'
+              },
+              {
+                'time': '2017-06-04T22:44:59.508Z',
+                'value': '0.0024469386784827982'
+              },
+              {
+                'time': '2017-06-04T22:45:59.508Z',
+                'value': '0.0026192823809523787'
+              },
+              {
+                'time': '2017-06-04T22:46:59.508Z',
+                'value': '0.003451999542852798'
+              },
+              {
+                'time': '2017-06-04T22:47:59.508Z',
+                'value': '0.0031780314285714288'
+              },
+              {
+                'time': '2017-06-04T22:48:59.508Z',
+                'value': '0.0024403352380952415'
+              },
+              {
+                'time': '2017-06-04T22:49:59.508Z',
+                'value': '0.001998824761904764'
+              },
+              {
+                'time': '2017-06-04T22:50:59.508Z',
+                'value': '0.0023792404761904806'
+              },
+              {
+                'time': '2017-06-04T22:51:59.508Z',
+                'value': '0.002725906190476185'
+              },
+              {
+                'time': '2017-06-04T22:52:59.508Z',
+                'value': '0.0020989528671155624'
+              },
+              {
+                'time': '2017-06-04T22:53:59.508Z',
+                'value': '0.00228808226745016'
+              },
+              {
+                'time': '2017-06-04T22:54:59.508Z',
+                'value': '0.0019860807413192147'
+              },
+              {
+                'time': '2017-06-04T22:55:59.508Z',
+                'value': '0.0022698085714285897'
+              },
+              {
+                'time': '2017-06-04T22:56:59.508Z',
+                'value': '0.0022839098467604415'
+              },
+              {
+                'time': '2017-06-04T22:57:59.508Z',
+                'value': '0.002531114761904749'
+              },
+              {
+                'time': '2017-06-04T22:58:59.508Z',
+                'value': '0.0028941072550999016'
+              },
+              {
+                'time': '2017-06-04T22:59:59.508Z',
+                'value': '0.002547169523809506'
+              },
+              {
+                'time': '2017-06-04T23:00:59.508Z',
+                'value': '0.0024062999999999958'
+              },
+              {
+                'time': '2017-06-04T23:01:59.508Z',
+                'value': '0.0026939518471604386'
+              },
+              {
+                'time': '2017-06-04T23:02:59.508Z',
+                'value': '0.002362901428571429'
+              },
+              {
+                'time': '2017-06-04T23:03:59.508Z',
+                'value': '0.002663927142857154'
+              },
+              {
+                'time': '2017-06-04T23:04:59.508Z',
+                'value': '0.0026173314285714354'
+              },
+              {
+                'time': '2017-06-04T23:05:59.508Z',
+                'value': '0.002326527366406044'
+              },
+              {
+                'time': '2017-06-04T23:06:59.508Z',
+                'value': '0.002035313809523809'
+              },
+              {
+                'time': '2017-06-04T23:07:59.508Z',
+                'value': '0.002421447414786533'
+              },
+              {
+                'time': '2017-06-04T23:08:59.508Z',
+                'value': '0.002898313809523804'
+              },
+              {
+                'time': '2017-06-04T23:09:59.508Z',
+                'value': '0.002544891856112907'
+              },
+              {
+                'time': '2017-06-04T23:10:59.508Z',
+                'value': '0.002290625356938882'
+              },
+              {
+                'time': '2017-06-04T23:11:59.508Z',
+                'value': '0.002483028095238096'
+              },
+              {
+                'time': '2017-06-04T23:12:59.508Z',
+                'value': '0.0023396832350784237'
+              },
+              {
+                'time': '2017-06-04T23:13:59.508Z',
+                'value': '0.002085529248176153'
+              },
+              {
+                'time': '2017-06-04T23:14:59.508Z',
+                'value': '0.0022417815068428012'
+              },
+              {
+                'time': '2017-06-04T23:15:59.508Z',
+                'value': '0.002660293333333341'
+              },
+              {
+                'time': '2017-06-04T23:16:59.508Z',
+                'value': '0.0029845149093818226'
+              },
+              {
+                'time': '2017-06-04T23:17:59.508Z',
+                'value': '0.0027716655079475464'
+              },
+              {
+                'time': '2017-06-04T23:18:59.508Z',
+                'value': '0.0025217708908741128'
+              },
+              {
+                'time': '2017-06-04T23:19:59.508Z',
+                'value': '0.0025811235131094055'
+              },
+              {
+                'time': '2017-06-04T23:20:59.508Z',
+                'value': '0.002209904761904762'
+              },
+              {
+                'time': '2017-06-04T23:21:59.508Z',
+                'value': '0.0025053322926383344'
+              },
+              {
+                'time': '2017-06-04T23:22:59.508Z',
+                'value': '0.002350917636526411'
+              },
+              {
+                'time': '2017-06-04T23:23:59.508Z',
+                'value': '0.0018477500000000078'
+              },
+              {
+                'time': '2017-06-04T23:24:59.508Z',
+                'value': '0.002427629523809527'
+              },
+              {
+                'time': '2017-06-04T23:25:59.508Z',
+                'value': '0.0019305498147601655'
+              },
+              {
+                'time': '2017-06-04T23:26:59.508Z',
+                'value': '0.002097250000000006'
+              },
+              {
+                'time': '2017-06-04T23:27:59.508Z',
+                'value': '0.002675020952780041'
+              },
+              {
+                'time': '2017-06-04T23:28:59.508Z',
+                'value': '0.0023142214285714374'
+              },
+              {
+                'time': '2017-06-04T23:29:59.508Z',
+                'value': '0.0023644723809523737'
+              },
+              {
+                'time': '2017-06-04T23:30:59.508Z',
+                'value': '0.002108696190476198'
+              },
+              {
+                'time': '2017-06-04T23:31:59.508Z',
+                'value': '0.0019918289697997194'
+              },
+              {
+                'time': '2017-06-04T23:32:59.508Z',
+                'value': '0.001583584285714283'
+              },
+              {
+                'time': '2017-06-04T23:33:59.508Z',
+                'value': '0.002073770226383112'
+              },
+              {
+                'time': '2017-06-04T23:34:59.508Z',
+                'value': '0.0025877664234966818'
+              },
+              {
+                'time': '2017-06-04T23:35:59.508Z',
+                'value': '0.0021138238095238147'
+              },
+              {
+                'time': '2017-06-04T23:36:59.508Z',
+                'value': '0.0022140838095238303'
+              },
+              {
+                'time': '2017-06-04T23:37:59.508Z',
+                'value': '0.0018592674425248847'
+              },
+              {
+                'time': '2017-06-04T23:38:59.508Z',
+                'value': '0.0020461969533657016'
+              },
+              {
+                'time': '2017-06-04T23:39:59.508Z',
+                'value': '0.0021593628571428543'
+              },
+              {
+                'time': '2017-06-04T23:40:59.508Z',
+                'value': '0.0024330682564928188'
+              },
+              {
+                'time': '2017-06-04T23:41:59.508Z',
+                'value': '0.0021501804779093174'
+              },
+              {
+                'time': '2017-06-04T23:42:59.508Z',
+                'value': '0.0025787493928397945'
+              },
+              {
+                'time': '2017-06-04T23:43:59.508Z',
+                'value': '0.002593657082448396'
+              },
+              {
+                'time': '2017-06-04T23:44:59.508Z',
+                'value': '0.0021316752380952306'
+              },
+              {
+                'time': '2017-06-04T23:45:59.508Z',
+                'value': '0.0026972905019952086'
+              },
+              {
+                'time': '2017-06-04T23:46:59.508Z',
+                'value': '0.002580250764292983'
+              },
+              {
+                'time': '2017-06-04T23:47:59.508Z',
+                'value': '0.00227103000000001'
+              },
+              {
+                'time': '2017-06-04T23:48:59.508Z',
+                'value': '0.0023678515647321146'
+              },
+              {
+                'time': '2017-06-04T23:49:59.508Z',
+                'value': '0.002371472857142866'
+              },
+              {
+                'time': '2017-06-04T23:50:59.508Z',
+                'value': '0.0026181353688500978'
+              },
+              {
+                'time': '2017-06-04T23:51:59.508Z',
+                'value': '0.0025609667711121217'
+              },
+              {
+                'time': '2017-06-04T23:52:59.508Z',
+                'value': '0.0027145308139922557'
+              },
+              {
+                'time': '2017-06-04T23:53:59.508Z',
+                'value': '0.0024249397613310512'
+              },
+              {
+                'time': '2017-06-04T23:54:59.508Z',
+                'value': '0.002399907142857147'
+              },
+              {
+                'time': '2017-06-04T23:55:59.508Z',
+                'value': '0.0024753357142857195'
+              },
+              {
+                'time': '2017-06-04T23:56:59.508Z',
+                'value': '0.0026179149325231575'
+              },
+              {
+                'time': '2017-06-04T23:57:59.508Z',
+                'value': '0.0024261340368186956'
+              },
+              {
+                'time': '2017-06-04T23:58:59.508Z',
+                'value': '0.0021061071428571517'
+              },
+              {
+                'time': '2017-06-04T23:59:59.508Z',
+                'value': '0.0024033971105037015'
+              },
+              {
+                'time': '2017-06-05T00:00:59.508Z',
+                'value': '0.0028287676190475956'
+              },
+              {
+                'time': '2017-06-05T00:01:59.508Z',
+                'value': '0.002499719050294778'
+              },
+              {
+                'time': '2017-06-05T00:02:59.508Z',
+                'value': '0.0026726102153353856'
+              },
+              {
+                'time': '2017-06-05T00:03:59.508Z',
+                'value': '0.00262582619047618'
+              },
+              {
+                'time': '2017-06-05T00:04:59.508Z',
+                'value': '0.002280473147363316'
+              },
+              {
+                'time': '2017-06-05T00:05:59.508Z',
+                'value': '0.002095581470652675'
+              },
+              {
+                'time': '2017-06-05T00:06:59.508Z',
+                'value': '0.002270768490828408'
+              },
+              {
+                'time': '2017-06-05T00:07:59.508Z',
+                'value': '0.002728577415023017'
+              },
+              {
+                'time': '2017-06-05T00:08:59.508Z',
+                'value': '0.002652512857142863'
+              },
+              {
+                'time': '2017-06-05T00:09:59.508Z',
+                'value': '0.0022781033924455674'
+              },
+              {
+                'time': '2017-06-05T00:10:59.508Z',
+                'value': '0.0025345038095238234'
+              },
+              {
+                'time': '2017-06-05T00:11:59.508Z',
+                'value': '0.002376050020000397'
+              },
+              {
+                'time': '2017-06-05T00:12:59.508Z',
+                'value': '0.002455068143506122'
+              },
+              {
+                'time': '2017-06-05T00:13:59.508Z',
+                'value': '0.002826705714285719'
+              },
+              {
+                'time': '2017-06-05T00:14:59.508Z',
+                'value': '0.002343833692070314'
+              },
+              {
+                'time': '2017-06-05T00:15:59.508Z',
+                'value': '0.00264853297122164'
+              },
+              {
+                'time': '2017-06-05T00:16:59.508Z',
+                'value': '0.0027656335117426257'
+              },
+              {
+                'time': '2017-06-05T00:17:59.508Z',
+                'value': '0.0025896543842439564'
+              },
+              {
+                'time': '2017-06-05T00:18:59.508Z',
+                'value': '0.002180053237081201'
+              },
+              {
+                'time': '2017-06-05T00:19:59.508Z',
+                'value': '0.002475245002333342'
+              },
+              {
+                'time': '2017-06-05T00:20:59.508Z',
+                'value': '0.0027559767805101065'
+              },
+              {
+                'time': '2017-06-05T00:21:59.508Z',
+                'value': '0.0022294836141296607'
+              },
+              {
+                'time': '2017-06-05T00:22:59.508Z',
+                'value': '0.0021383590476190643'
+              },
+              {
+                'time': '2017-06-05T00:23:59.508Z',
+                'value': '0.002085417956361494'
+              },
+              {
+                'time': '2017-06-05T00:24:59.508Z',
+                'value': '0.0024140319047619013'
+              },
+              {
+                'time': '2017-06-05T00:25:59.508Z',
+                'value': '0.0024513114285714304'
+              },
+              {
+                'time': '2017-06-05T00:26:59.508Z',
+                'value': '0.0026932152380952446'
+              },
+              {
+                'time': '2017-06-05T00:27:59.508Z',
+                'value': '0.0022656844350898517'
+              },
+              {
+                'time': '2017-06-05T00:28:59.508Z',
+                'value': '0.0024483785714285704'
+              },
+              {
+                'time': '2017-06-05T00:29:59.508Z',
+                'value': '0.002559505804817207'
+              },
+              {
+                'time': '2017-06-05T00:30:59.508Z',
+                'value': '0.0019485681088751649'
+              },
+              {
+                'time': '2017-06-05T00:31:59.508Z',
+                'value': '0.00228367984456996'
+              },
+              {
+                'time': '2017-06-05T00:32:59.508Z',
+                'value': '0.002522149047619049'
+              },
+              {
+                'time': '2017-06-05T00:33:59.508Z',
+                'value': '0.0026860117715406737'
+              },
+              {
+                'time': '2017-06-05T00:34:59.508Z',
+                'value': '0.002679669523809523'
+              },
+              {
+                'time': '2017-06-05T00:35:59.508Z',
+                'value': '0.0022201920970675937'
+              },
+              {
+                'time': '2017-06-05T00:36:59.508Z',
+                'value': '0.0022917647619047615'
+              },
+              {
+                'time': '2017-06-05T00:37:59.508Z',
+                'value': '0.0021774059294673576'
+              },
+              {
+                'time': '2017-06-05T00:38:59.508Z',
+                'value': '0.0024637766666666763'
+              },
+              {
+                'time': '2017-06-05T00:39:59.508Z',
+                'value': '0.002470468290174195'
+              },
+              {
+                'time': '2017-06-05T00:40:59.508Z',
+                'value': '0.0022188616082057812'
+              },
+              {
+                'time': '2017-06-05T00:41:59.508Z',
+                'value': '0.002421840744373875'
+              },
+              {
+                'time': '2017-06-05T00:42:59.508Z',
+                'value': '0.0023918266666666547'
+              },
+              {
+                'time': '2017-06-05T00:43:59.508Z',
+                'value': '0.002195743809523809'
+              },
+              {
+                'time': '2017-06-05T00:44:59.508Z',
+                'value': '0.0025514828571428687'
+              },
+              {
+                'time': '2017-06-05T00:45:59.508Z',
+                'value': '0.0027981709349612694'
+              },
+              {
+                'time': '2017-06-05T00:46:59.508Z',
+                'value': '0.002557977142857146'
+              },
+              {
+                'time': '2017-06-05T00:47:59.508Z',
+                'value': '0.002213244285714286'
+              },
+              {
+                'time': '2017-06-05T00:48:59.508Z',
+                'value': '0.0025706738095238046'
+              },
+              {
+                'time': '2017-06-05T00:49:59.508Z',
+                'value': '0.002210976666666671'
+              },
+              {
+                'time': '2017-06-05T00:50:59.508Z',
+                'value': '0.002055377091646749'
+              },
+              {
+                'time': '2017-06-05T00:51:59.508Z',
+                'value': '0.002308368095238119'
+              },
+              {
+                'time': '2017-06-05T00:52:59.508Z',
+                'value': '0.0024687939885141615'
+              },
+              {
+                'time': '2017-06-05T00:53:59.508Z',
+                'value': '0.002563018571428578'
+              },
+              {
+                'time': '2017-06-05T00:54:59.508Z',
+                'value': '0.00240563291078959'
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  },
+  {
+    'title': 'Memory usage',
+    'weight': 1,
+    'y_label': 'Values',
+    'queries': [
+      {
+        'query_range': 'avg(container_memory_usage_bytes{%{environment_filter}}) / 2^20',
+        'label': 'Container memory',
+        'unit': 'MiB',
+        'result': [
+          {
+            'metric': {
+
+            },
+            'values': [
+              {
+                'time': '2017-06-04T21:22:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:23:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:24:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:25:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:26:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:27:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:28:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:29:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:30:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:31:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:32:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:33:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:34:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:35:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:36:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:37:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:38:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:39:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:40:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:41:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:42:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:43:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:44:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:45:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:46:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:47:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:48:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:49:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:50:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:51:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:52:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:53:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:54:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:55:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:56:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:57:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:58:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T21:59:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:00:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:01:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:02:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:03:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:04:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:05:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:06:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:07:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:08:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:09:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:10:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:11:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:12:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:13:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:14:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:15:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:16:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:17:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:18:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:19:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:20:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:21:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:22:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:23:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:24:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:25:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:26:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:27:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:28:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:29:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:30:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:31:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:32:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:33:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:34:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:35:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:36:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:37:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:38:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:39:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:40:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:41:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:42:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:43:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:44:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:45:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:46:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:47:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:48:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:49:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:50:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:51:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:52:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:53:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:54:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:55:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:56:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:57:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:58:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T22:59:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:00:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:01:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:02:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:03:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:04:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:05:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:06:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:07:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:08:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:09:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:10:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:11:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:12:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:13:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:14:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:15:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:16:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:17:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:18:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:19:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:20:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:21:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:22:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:23:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:24:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:25:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:26:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:27:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:28:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:29:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:30:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:31:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:32:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:33:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:34:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:35:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:36:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:37:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:38:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:39:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:40:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:41:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:42:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:43:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:44:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:45:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:46:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:47:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:48:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:49:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:50:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:51:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:52:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:53:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:54:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:55:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:56:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:57:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:58:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-04T23:59:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:00:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:01:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:02:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:03:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:04:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:05:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:06:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:07:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:08:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:09:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:10:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:11:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:12:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:13:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:14:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:15:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:16:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:17:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:18:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:19:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:20:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:21:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:22:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:23:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:24:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:25:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:26:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:27:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:28:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:29:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:30:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:31:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:32:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:33:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:34:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:35:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:36:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:37:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:38:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:39:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:40:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:41:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:42:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:43:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:44:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:45:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:46:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:47:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:48:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:49:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:50:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:51:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:52:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:53:59.508Z',
+                'value': '15.0859375'
+              },
+              {
+                'time': '2017-06-05T00:54:59.508Z',
+                'value': '15.0859375'
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+];
+
+export function MonitorMockInterceptor(request, next) {
+  const body = responseMockData[request.method.toUpperCase()][request.url];
+
+  next(request.respondWith(JSON.stringify(body), {
+    status: 200,
+  }));
+}
diff --git a/spec/javascripts/monitoring/monitoring_column_spec.js b/spec/javascripts/monitoring/monitoring_column_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..b3bc97adc9f9e5979b791ccb6c9abbf05b7ed8c4
--- /dev/null
+++ b/spec/javascripts/monitoring/monitoring_column_spec.js
@@ -0,0 +1,108 @@
+import Vue from 'vue';
+import _ from 'underscore';
+import MonitoringColumn from '~/monitoring/components/monitoring_column.vue';
+import MonitoringMixins from '~/monitoring/mixins/monitoring_mixins';
+import eventHub from '~/monitoring/event_hub';
+import { deploymentData, singleRowMetrics } from './mock_data';
+
+const createComponent = (propsData) => {
+  const Component = Vue.extend(MonitoringColumn);
+
+  return new Component({
+    propsData,
+  }).$mount();
+};
+
+describe('MonitoringColumn', () => {
+  beforeEach(() => {
+    spyOn(MonitoringMixins.methods, 'formatDeployments').and.callFake(function fakeFormat() {
+      return {};
+    });
+  });
+
+  it('has a title', () => {
+    const component = createComponent({
+      columnData: singleRowMetrics[0],
+      classType: 'col-md-6',
+      updateAspectRatio: false,
+      deploymentData,
+    });
+
+    expect(component.$el.querySelector('.text-center').innerText.trim()).toBe(component.columnData.title);
+  });
+
+  it('creates a path for the line and area of the graph', (done) => {
+    const component = createComponent({
+      columnData: singleRowMetrics[0],
+      classType: 'col-md-6',
+      updateAspectRatio: false,
+      deploymentData,
+    });
+
+    Vue.nextTick(() => {
+      expect(component.area).toBeDefined();
+      expect(component.line).toBeDefined();
+      expect(typeof component.area).toEqual('string');
+      expect(typeof component.line).toEqual('string');
+      expect(_.isFunction(component.xScale)).toBe(true);
+      expect(_.isFunction(component.yScale)).toBe(true);
+      done();
+    });
+  });
+
+  describe('Computed props', () => {
+    it('axisTransform translates an element Y position depending of its height', () => {
+      const component = createComponent({
+        columnData: singleRowMetrics[0],
+        classType: 'col-md-6',
+        updateAspectRatio: false,
+        deploymentData,
+      });
+
+      const transformedHeight = `${component.graphHeight - 100}`;
+      expect(component.axisTransform.indexOf(transformedHeight))
+        .not.toEqual(-1);
+    });
+
+    it('outterViewBox gets a width and height property based on the DOM size of the element', () => {
+      const component = createComponent({
+        columnData: singleRowMetrics[0],
+        classType: 'col-md-6',
+        updateAspectRatio: false,
+        deploymentData,
+      });
+
+      const viewBoxArray = component.outterViewBox.split(' ');
+      expect(typeof component.outterViewBox).toEqual('string');
+      expect(viewBoxArray[2]).toEqual(component.graphWidth.toString());
+      expect(viewBoxArray[3]).toEqual(component.graphHeight.toString());
+    });
+  });
+
+  it('sends an event to the eventhub when it has finished resizing', (done) => {
+    const component = createComponent({
+      columnData: singleRowMetrics[0],
+      classType: 'col-md-6',
+      updateAspectRatio: false,
+      deploymentData,
+    });
+    spyOn(eventHub, '$emit');
+
+    component.updateAspectRatio = true;
+    Vue.nextTick(() => {
+      expect(eventHub.$emit).toHaveBeenCalled();
+      done();
+    });
+  });
+
+  it('has a title for the y-axis that comes from the backend', () => {
+    const component = createComponent({
+      columnData: singleRowMetrics[0],
+      classType: 'col-md-6',
+      updateAspectRatio: false,
+      deploymentData,
+    });
+
+    expect(component.yAxisLabel).toEqual(component.columnData.y_label);
+  });
+});
diff --git a/spec/javascripts/monitoring/monitoring_deployment_spec.js b/spec/javascripts/monitoring/monitoring_deployment_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..5cc5b5148240a1393ac8ecce2304023848d36b6e
--- /dev/null
+++ b/spec/javascripts/monitoring/monitoring_deployment_spec.js
@@ -0,0 +1,137 @@
+import Vue from 'vue';
+import MonitoringState from '~/monitoring/components/monitoring_deployment.vue';
+import { deploymentData } from './mock_data';
+
+const createComponent = (propsData) => {
+  const Component = Vue.extend(MonitoringState);
+
+  return new Component({
+    propsData,
+  }).$mount();
+};
+
+describe('MonitoringDeployment', () => {
+  const reducedDeploymentData = [deploymentData[0]];
+  reducedDeploymentData[0].ref = reducedDeploymentData[0].ref.name;
+  reducedDeploymentData[0].xPos = 10;
+  reducedDeploymentData[0].time = new Date(reducedDeploymentData[0].created_at);
+  describe('Methods', () => {
+    it('refText shows the ref when a tag is available', () => {
+      reducedDeploymentData[0].tag = '1.0';
+      const component = createComponent({
+        showDeployInfo: false,
+        deploymentData: reducedDeploymentData,
+        graphHeight: 300,
+        graphHeightOffset: 120,
+      });
+
+      expect(
+        component.refText(reducedDeploymentData[0]),
+      ).toEqual(reducedDeploymentData[0].ref);
+    });
+
+    it('refText shows the sha when no tag is available', () => {
+      reducedDeploymentData[0].tag = null;
+      const component = createComponent({
+        showDeployInfo: false,
+        deploymentData: reducedDeploymentData,
+        graphHeight: 300,
+        graphHeightOffset: 120,
+      });
+
+      expect(
+        component.refText(reducedDeploymentData[0]),
+      ).toContain('f5bcd1');
+    });
+
+    it('nameDeploymentClass creates a class with the prefix deploy-info-', () => {
+      const component = createComponent({
+        showDeployInfo: false,
+        deploymentData: reducedDeploymentData,
+        graphHeight: 300,
+        graphHeightOffset: 120,
+      });
+
+      expect(
+        component.nameDeploymentClass(reducedDeploymentData[0]),
+      ).toContain('deploy-info');
+    });
+
+    it('transformDeploymentGroup translates an available deployment', () => {
+      const component = createComponent({
+        showDeployInfo: false,
+        deploymentData: reducedDeploymentData,
+        graphHeight: 300,
+        graphHeightOffset: 120,
+      });
+
+      expect(
+        component.transformDeploymentGroup(reducedDeploymentData[0]),
+      ).toContain('translate(11, 20)');
+    });
+
+    it('hides the deployment flag', () => {
+      reducedDeploymentData[0].showDeploymentFlag = false;
+      const component = createComponent({
+        showDeployInfo: true,
+        deploymentData: reducedDeploymentData,
+        graphHeight: 300,
+        graphHeightOffset: 120,
+      });
+
+      expect(component.$el.querySelector('.js-deploy-info-box')).toBeNull();
+    });
+
+    it('shows the deployment flag', () => {
+      reducedDeploymentData[0].showDeploymentFlag = true;
+      const component = createComponent({
+        showDeployInfo: true,
+        deploymentData: reducedDeploymentData,
+        graphHeight: 300,
+        graphHeightOffset: 120,
+      });
+
+      expect(
+        component.$el.querySelector('.js-deploy-info-box').style.display,
+      ).not.toEqual('display: none;');
+    });
+
+    it('shows the refText inside a text element with the deploy-info-text class', () => {
+      reducedDeploymentData[0].showDeploymentFlag = true;
+      const component = createComponent({
+        showDeployInfo: true,
+        deploymentData: reducedDeploymentData,
+        graphHeight: 300,
+        graphHeightOffset: 120,
+      });
+
+      expect(
+        component.$el.querySelector('.deploy-info-text').firstChild.nodeValue.trim(),
+      ).toEqual(component.refText(reducedDeploymentData[0]));
+    });
+
+    it('should contain a hidden gradient', () => {
+      const component = createComponent({
+        showDeployInfo: true,
+        deploymentData: reducedDeploymentData,
+        graphHeight: 300,
+        graphHeightOffset: 120,
+      });
+
+      expect(component.$el.querySelector('#shadow-gradient')).not.toBeNull();
+    });
+
+    describe('Computed props', () => {
+      it('calculatedHeight', () => {
+        const component = createComponent({
+          showDeployInfo: true,
+          deploymentData: reducedDeploymentData,
+          graphHeight: 300,
+          graphHeightOffset: 120,
+        });
+
+        expect(component.calculatedHeight).toEqual(180);
+      });
+    });
+  });
+});
diff --git a/spec/javascripts/monitoring/monitoring_flag_spec.js b/spec/javascripts/monitoring/monitoring_flag_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..3861a95ff073b2af181fb5b81dbeb985e20a52f6
--- /dev/null
+++ b/spec/javascripts/monitoring/monitoring_flag_spec.js
@@ -0,0 +1,76 @@
+import Vue from 'vue';
+import MonitoringFlag from '~/monitoring/components/monitoring_flag.vue';
+
+const createComponent = (propsData) => {
+  const Component = Vue.extend(MonitoringFlag);
+
+  return new Component({
+    propsData,
+  }).$mount();
+};
+
+function getCoordinate(component, selector, coordinate) {
+  const coordinateVal = component.$el.querySelector(selector).getAttribute(coordinate);
+  return parseInt(coordinateVal, 10);
+}
+
+describe('MonitoringFlag', () => {
+  it('has a line and a circle located at the currentXCoordinate and currentYCoordinate', () => {
+    const component = createComponent({
+      currentXCoordinate: 200,
+      currentYCoordinate: 100,
+      currentFlagPosition: 100,
+      currentData: {
+        time: new Date('2017-06-04T18:17:33.501Z'),
+        value: '1.49609375',
+      },
+      graphHeight: 300,
+      graphHeightOffset: 120,
+    });
+
+    expect(getCoordinate(component, '.selected-metric-line', 'x1'))
+      .toEqual(component.currentXCoordinate);
+    expect(getCoordinate(component, '.selected-metric-line', 'x2'))
+      .toEqual(component.currentXCoordinate);
+    expect(getCoordinate(component, '.circle-metric', 'cx'))
+      .toEqual(component.currentXCoordinate);
+    expect(getCoordinate(component, '.circle-metric', 'cy'))
+      .toEqual(component.currentYCoordinate);
+  });
+
+  it('has a SVG with the class rect-text-metric at the currentFlagPosition', () => {
+    const component = createComponent({
+      currentXCoordinate: 200,
+      currentYCoordinate: 100,
+      currentFlagPosition: 100,
+      currentData: {
+        time: new Date('2017-06-04T18:17:33.501Z'),
+        value: '1.49609375',
+      },
+      graphHeight: 300,
+      graphHeightOffset: 120,
+    });
+
+    const svg = component.$el.querySelector('.rect-text-metric');
+    expect(svg.tagName).toEqual('svg');
+    expect(parseInt(svg.getAttribute('x'), 10)).toEqual(component.currentFlagPosition);
+  });
+
+  describe('Computed props', () => {
+    it('calculatedHeight', () => {
+      const component = createComponent({
+        currentXCoordinate: 200,
+        currentYCoordinate: 100,
+        currentFlagPosition: 100,
+        currentData: {
+          time: new Date('2017-06-04T18:17:33.501Z'),
+          value: '1.49609375',
+        },
+        graphHeight: 300,
+        graphHeightOffset: 120,
+      });
+
+      expect(component.calculatedHeight).toEqual(180);
+    });
+  });
+});
diff --git a/spec/javascripts/monitoring/monitoring_legends_spec.js b/spec/javascripts/monitoring/monitoring_legends_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..4c69b81e6501f9788af01428f47b86c7547b1073
--- /dev/null
+++ b/spec/javascripts/monitoring/monitoring_legends_spec.js
@@ -0,0 +1,111 @@
+import Vue from 'vue';
+import MonitoringLegends from '~/monitoring/components/monitoring_legends.vue';
+import measurements from '~/monitoring/utils/measurements';
+
+const createComponent = (propsData) => {
+  const Component = Vue.extend(MonitoringLegends);
+
+  return new Component({
+    propsData,
+  }).$mount();
+};
+
+function getTextFromNode(component, selector) {
+  return component.$el.querySelector(selector).firstChild.nodeValue.trim();
+}
+
+describe('MonitoringLegends', () => {
+  describe('Computed props', () => {
+    it('textTransform', () => {
+      const component = createComponent({
+        graphWidth: 500,
+        graphHeight: 300,
+        margin: measurements.large.margin,
+        measurements: measurements.large,
+        areaColorRgb: '#f0f0f0',
+        legendTitle: 'Title',
+        yAxisLabel: 'Values',
+        metricUsage: 'Value',
+      });
+
+      expect(component.textTransform).toContain('translate(15, 120) rotate(-90)');
+    });
+
+    it('xPosition', () => {
+      const component = createComponent({
+        graphWidth: 500,
+        graphHeight: 300,
+        margin: measurements.large.margin,
+        measurements: measurements.large,
+        areaColorRgb: '#f0f0f0',
+        legendTitle: 'Title',
+        yAxisLabel: 'Values',
+        metricUsage: 'Value',
+      });
+
+      expect(component.xPosition).toEqual(180);
+    });
+
+    it('yPosition', () => {
+      const component = createComponent({
+        graphWidth: 500,
+        graphHeight: 300,
+        margin: measurements.large.margin,
+        measurements: measurements.large,
+        areaColorRgb: '#f0f0f0',
+        legendTitle: 'Title',
+        yAxisLabel: 'Values',
+        metricUsage: 'Value',
+      });
+
+      expect(component.yPosition).toEqual(240);
+    });
+
+    it('rectTransform', () => {
+      const component = createComponent({
+        graphWidth: 500,
+        graphHeight: 300,
+        margin: measurements.large.margin,
+        measurements: measurements.large,
+        areaColorRgb: '#f0f0f0',
+        legendTitle: 'Title',
+        yAxisLabel: 'Values',
+        metricUsage: 'Value',
+      });
+
+      expect(component.rectTransform).toContain('translate(0, 120) rotate(-90)');
+    });
+  });
+
+  it('has 2 rect-axis-text rect svg elements', () => {
+    const component = createComponent({
+      graphWidth: 500,
+      graphHeight: 300,
+      margin: measurements.large.margin,
+      measurements: measurements.large,
+      areaColorRgb: '#f0f0f0',
+      legendTitle: 'Title',
+      yAxisLabel: 'Values',
+      metricUsage: 'Value',
+    });
+
+    expect(component.$el.querySelectorAll('.rect-axis-text').length).toEqual(2);
+  });
+
+  it('contains text to signal the usage, title and time', () => {
+    const component = createComponent({
+      graphWidth: 500,
+      graphHeight: 300,
+      margin: measurements.large.margin,
+      measurements: measurements.large,
+      areaColorRgb: '#f0f0f0',
+      legendTitle: 'Title',
+      yAxisLabel: 'Values',
+      metricUsage: 'Value',
+    });
+
+    expect(getTextFromNode(component, '.text-metric-title')).toEqual(component.legendTitle);
+    expect(getTextFromNode(component, '.text-metric-usage')).toEqual(component.metricUsage);
+    expect(getTextFromNode(component, '.label-axis-text')).toEqual(component.yAxisLabel);
+  });
+});
diff --git a/spec/javascripts/monitoring/monitoring_row_spec.js b/spec/javascripts/monitoring/monitoring_row_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..a82480e8342ed5c3381cf998c20cd8abac604566
--- /dev/null
+++ b/spec/javascripts/monitoring/monitoring_row_spec.js
@@ -0,0 +1,57 @@
+import Vue from 'vue';
+import MonitoringRow from '~/monitoring/components/monitoring_row.vue';
+import { deploymentData, singleRowMetrics } from './mock_data';
+
+const createComponent = (propsData) => {
+  const Component = Vue.extend(MonitoringRow);
+
+  return new Component({
+    propsData,
+  }).$mount();
+};
+
+describe('MonitoringRow', () => {
+  describe('Computed props', () => {
+    it('bootstrapClass is set to col-md-6 when rowData is higher/equal to 2', () => {
+      const component = createComponent({
+        rowData: singleRowMetrics,
+        updateAspectRatio: false,
+        deploymentData,
+      });
+
+      expect(component.bootstrapClass).toEqual('col-md-6');
+    });
+
+    it('bootstrapClass is set to col-md-12 when rowData is lower than 2', () => {
+      const component = createComponent({
+        rowData: [singleRowMetrics[0]],
+        updateAspectRatio: false,
+        deploymentData,
+      });
+
+      expect(component.bootstrapClass).toEqual('col-md-12');
+    });
+  });
+
+  it('has one column', () => {
+    const component = createComponent({
+      rowData: singleRowMetrics,
+      updateAspectRatio: false,
+      deploymentData,
+    });
+
+    expect(component.$el.querySelectorAll('.prometheus-svg-container').length)
+        .toEqual(component.rowData.length);
+  });
+
+  it('has two columns', () => {
+    const component = createComponent({
+      rowData: singleRowMetrics,
+      updateAspectRatio: false,
+      deploymentData,
+    });
+
+    expect(component.$el.querySelectorAll('.col-md-6').length)
+        .toEqual(component.rowData.length);
+  });
+});
diff --git a/spec/javascripts/monitoring/monitoring_spec.js b/spec/javascripts/monitoring/monitoring_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..6c7b691baa4699a2f8e2d8928b06800bf1659a69
--- /dev/null
+++ b/spec/javascripts/monitoring/monitoring_spec.js
@@ -0,0 +1,49 @@
+import Vue from 'vue';
+import Monitoring from '~/monitoring/components/monitoring.vue';
+import { MonitorMockInterceptor } from './mock_data';
+
+describe('Monitoring', () => {
+  const fixtureName = 'environments/metrics/metrics.html.raw';
+  let MonitoringComponent;
+  let component;
+  preloadFixtures(fixtureName);
+
+  beforeEach(() => {
+    loadFixtures(fixtureName);
+    MonitoringComponent = Vue.extend(Monitoring);
+  });
+
+  describe('no metrics are available yet', () => {
+    it('shows a getting started empty state when no metrics are present', () => {
+      component = new MonitoringComponent({
+        el: document.querySelector('#prometheus-graphs'),
+      });
+
+      component.$mount();
+      expect(component.$el.querySelector('#prometheus-graphs')).toBe(null);
+      expect(component.state).toEqual('gettingStarted');
+    });
+  });
+
+  describe('requests information to the server', () => {
+    beforeEach(() => {
+      document.querySelector('#prometheus-graphs').setAttribute('data-has-metrics', 'true');
+      Vue.http.interceptors.push(MonitorMockInterceptor);
+    });
+
+    afterEach(() => {
+      Vue.http.interceptors = _.without(Vue.http.interceptors, MonitorMockInterceptor);
+    });
+
+    it('shows up a loading state', (done) => {
+      component = new MonitoringComponent({
+        el: document.querySelector('#prometheus-graphs'),
+      });
+      component.$mount();
+      Vue.nextTick(() => {
+        expect(component.state).toEqual('loading');
+        done();
+      });
+    });
+  });
+});
diff --git a/spec/javascripts/monitoring/monitoring_state_spec.js b/spec/javascripts/monitoring/monitoring_state_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..4c0c558502fbb3ad6195695dea94d3cfa016cfa1
--- /dev/null
+++ b/spec/javascripts/monitoring/monitoring_state_spec.js
@@ -0,0 +1,110 @@
+import Vue from 'vue';
+import MonitoringState from '~/monitoring/components/monitoring_state.vue';
+import { statePaths } from './mock_data';
+
+const createComponent = (propsData) => {
+  const Component = Vue.extend(MonitoringState);
+
+  return new Component({
+    propsData,
+  }).$mount();
+};
+
+function getTextFromNode(component, selector) {
+  return component.$el.querySelector(selector).firstChild.nodeValue.trim();
+}
+
+describe('MonitoringState', () => {
+  describe('Computed props', () => {
+    it('currentState', () => {
+      const component = createComponent({
+        selectedState: 'gettingStarted',
+        settingsPath: statePaths.settingsPath,
+        documentationPath: statePaths.documentationPath,
+      });
+
+      expect(component.currentState).toBe(component.states.gettingStarted);
+    });
+
+    it('buttonPath returns settings path for the state "gettingStarted"', () => {
+      const component = createComponent({
+        selectedState: 'gettingStarted',
+        settingsPath: statePaths.settingsPath,
+        documentationPath: statePaths.documentationPath,
+      });
+
+      expect(component.buttonPath).toEqual(statePaths.settingsPath);
+      expect(component.buttonPath).not.toEqual(statePaths.documentationPath);
+    });
+
+    it('buttonPath returns documentation path for any of the other states', () => {
+      const component = createComponent({
+        selectedState: 'loading',
+        settingsPath: statePaths.settingsPath,
+        documentationPath: statePaths.documentationPath,
+      });
+
+      expect(component.buttonPath).toEqual(statePaths.documentationPath);
+      expect(component.buttonPath).not.toEqual(statePaths.settingsPath);
+    });
+
+    it('showButtonDescription returns a description with a link for the unableToConnect state', () => {
+      const component = createComponent({
+        selectedState: 'unableToConnect',
+        settingsPath: statePaths.settingsPath,
+        documentationPath: statePaths.documentationPath,
+      });
+
+      expect(component.showButtonDescription).toEqual(true);
+    });
+
+    it('showButtonDescription returns the description without a link for any other state', () => {
+      const component = createComponent({
+        selectedState: 'loading',
+        settingsPath: statePaths.settingsPath,
+        documentationPath: statePaths.documentationPath,
+      });
+
+      expect(component.showButtonDescription).toEqual(false);
+    });
+  });
+
+  it('should show the gettingStarted state', () => {
+    const component = createComponent({
+      selectedState: 'gettingStarted',
+      settingsPath: statePaths.settingsPath,
+      documentationPath: statePaths.documentationPath,
+    });
+
+    expect(component.$el.querySelector('svg')).toBeDefined();
+    expect(getTextFromNode(component, '.state-title')).toEqual(component.states.gettingStarted.title);
+    expect(getTextFromNode(component, '.state-description')).toEqual(component.states.gettingStarted.description);
+    expect(getTextFromNode(component, '.btn-success')).toEqual(component.states.gettingStarted.buttonText);
+  });
+
+  it('should show the loading state', () => {
+    const component = createComponent({
+      selectedState: 'loading',
+      settingsPath: statePaths.settingsPath,
+      documentationPath: statePaths.documentationPath,
+    });
+
+    expect(component.$el.querySelector('svg')).toBeDefined();
+    expect(getTextFromNode(component, '.state-title')).toEqual(component.states.loading.title);
+    expect(getTextFromNode(component, '.state-description')).toEqual(component.states.loading.description);
+    expect(getTextFromNode(component, '.btn-success')).toEqual(component.states.loading.buttonText);
+  });
+
+  it('should show the unableToConnect state', () => {
+    const component = createComponent({
+      selectedState: 'unableToConnect',
+      settingsPath: statePaths.settingsPath,
+      documentationPath: statePaths.documentationPath,
+    });
+
+    expect(component.$el.querySelector('svg')).toBeDefined();
+    expect(getTextFromNode(component, '.state-title')).toEqual(component.states.unableToConnect.title);
+    expect(component.$el.querySelector('.state-description a')).toBeDefined();
+    expect(getTextFromNode(component, '.btn-success')).toEqual(component.states.unableToConnect.buttonText);
+  });
+});
diff --git a/spec/javascripts/monitoring/monitoring_store_spec.js b/spec/javascripts/monitoring/monitoring_store_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..20c1e6a0005779f2ac06da17b46a73d7cc1f55fb
--- /dev/null
+++ b/spec/javascripts/monitoring/monitoring_store_spec.js
@@ -0,0 +1,24 @@
+import MonitoringStore from '~/monitoring/stores/monitoring_store';
+import MonitoringMock, { deploymentData } from './mock_data';
+
+describe('MonitoringStore', () => {
+  this.store = new MonitoringStore();
+  this.store.storeMetrics(MonitoringMock.data);
+
+  it('contains one group that contains two queries sorted by priority in one row', () => {
+    expect(this.store.groups).toBeDefined();
+    expect(this.store.groups.length).toEqual(1);
+    expect(this.store.groups[0].metrics.length).toEqual(1);
+  });
+
+  it('gets the metrics count for every group', () => {
+    expect(this.store.getMetricsCount()).toEqual(2);
+  });
+
+  it('contains deployment data', () => {
+    this.store.storeDeploymentData(deploymentData);
+    expect(this.store.deploymentData).toBeDefined();
+    expect(this.store.deploymentData.length).toEqual(3);
+    expect(typeof this.store.deploymentData[0]).toEqual('object');
+  });
+});
diff --git a/spec/javascripts/monitoring/prometheus_graph_spec.js b/spec/javascripts/monitoring/prometheus_graph_spec.js
deleted file mode 100644
index 25578bf1c6e37abf2cf232ec1273179516a79097..0000000000000000000000000000000000000000
--- a/spec/javascripts/monitoring/prometheus_graph_spec.js
+++ /dev/null
@@ -1,98 +0,0 @@
-import 'jquery';
-import PrometheusGraph from '~/monitoring/prometheus_graph';
-import { prometheusMockData } from './prometheus_mock_data';
-
-describe('PrometheusGraph', () => {
-  const fixtureName = 'environments/metrics/metrics.html.raw';
-  const prometheusGraphContainer = '.prometheus-graph';
-  const prometheusGraphContents = `${prometheusGraphContainer}[graph-type=cpu_values]`;
-
-  preloadFixtures(fixtureName);
-
-  beforeEach(() => {
-    loadFixtures(fixtureName);
-    $('.prometheus-container').data('has-metrics', 'true');
-    this.prometheusGraph = new PrometheusGraph();
-    const self = this;
-    const fakeInit = (metricsResponse) => {
-      self.prometheusGraph.transformData(metricsResponse);
-      self.prometheusGraph.createGraph();
-    };
-    spyOn(this.prometheusGraph, 'init').and.callFake(fakeInit);
-  });
-
-  it('initializes graph properties', () => {
-    // Test for the measurements
-    expect(this.prometheusGraph.margin).toBeDefined();
-    expect(this.prometheusGraph.marginLabelContainer).toBeDefined();
-    expect(this.prometheusGraph.originalWidth).toBeDefined();
-    expect(this.prometheusGraph.originalHeight).toBeDefined();
-    expect(this.prometheusGraph.height).toBeDefined();
-    expect(this.prometheusGraph.width).toBeDefined();
-    expect(this.prometheusGraph.backOffRequestCounter).toBeDefined();
-    // Test for the graph properties (colors, radius, etc.)
-    expect(this.prometheusGraph.graphSpecificProperties).toBeDefined();
-    expect(this.prometheusGraph.commonGraphProperties).toBeDefined();
-  });
-
-  it('transforms the data', () => {
-    this.prometheusGraph.init(prometheusMockData.metrics);
-    Object.keys(this.prometheusGraph.graphSpecificProperties, (key) => {
-      const graphProps = this.prometheusGraph.graphSpecificProperties[key];
-      expect(graphProps.data).toBeDefined();
-      expect(graphProps.data.length).toBe(121);
-    });
-  });
-
-  it('creates two graphs', () => {
-    this.prometheusGraph.init(prometheusMockData.metrics);
-    expect($(prometheusGraphContainer).length).toBe(2);
-  });
-
-  describe('Graph contents', () => {
-    beforeEach(() => {
-      this.prometheusGraph.init(prometheusMockData.metrics);
-    });
-
-    it('has axis, an area, a line and a overlay', () => {
-      const $graphContainer = $(prometheusGraphContents).find('.x-axis').parent();
-      expect($graphContainer.find('.x-axis')).toBeDefined();
-      expect($graphContainer.find('.y-axis')).toBeDefined();
-      expect($graphContainer.find('.prometheus-graph-overlay')).toBeDefined();
-      expect($graphContainer.find('.metric-line')).toBeDefined();
-      expect($graphContainer.find('.metric-area')).toBeDefined();
-    });
-
-    it('has legends, labels and an extra axis that labels the metrics', () => {
-      const $prometheusGraphContents = $(prometheusGraphContents);
-      const $axisLabelContainer = $(prometheusGraphContents).find('.label-x-axis-line').parent();
-      expect($prometheusGraphContents.find('.label-x-axis-line')).toBeDefined();
-      expect($prometheusGraphContents.find('.label-y-axis-line')).toBeDefined();
-      expect($prometheusGraphContents.find('.label-axis-text')).toBeDefined();
-      expect($prometheusGraphContents.find('.rect-axis-text')).toBeDefined();
-      expect($axisLabelContainer.find('rect').length).toBe(3);
-      expect($axisLabelContainer.find('text').length).toBe(4);
-    });
-  });
-});
-
-describe('PrometheusGraphs UX states', () => {
-  const fixtureName = 'environments/metrics/metrics.html.raw';
-  preloadFixtures(fixtureName);
-
-  beforeEach(() => {
-    loadFixtures(fixtureName);
-    this.prometheusGraph = new PrometheusGraph();
-  });
-
-  it('shows a specified state', () => {
-    this.prometheusGraph.state = '.js-getting-started';
-    this.prometheusGraph.updateState();
-    const $state = $('.js-getting-started');
-    expect($state).toBeDefined();
-    expect($('.state-title', $state)).toBeDefined();
-    expect($('.state-svg', $state)).toBeDefined();
-    expect($('.state-description', $state)).toBeDefined();
-    expect($('.state-button', $state)).toBeDefined();
-  });
-});
diff --git a/spec/javascripts/monitoring/prometheus_mock_data.js b/spec/javascripts/monitoring/prometheus_mock_data.js
deleted file mode 100644
index 1cdc14faaa89e39218c7d640d8f9b315c21fc8ad..0000000000000000000000000000000000000000
--- a/spec/javascripts/monitoring/prometheus_mock_data.js
+++ /dev/null
@@ -1,1014 +0,0 @@
-/* eslint-disable import/prefer-default-export*/
-export const prometheusMockData = {
-  status: 200,
-  metrics: {
-    success: true,
-    metrics: {
-      memory_values: [
-        {
-          metric: {
-          },
-          values: [
-            [
-              1488462917.256,
-              '10.12890625',
-            ],
-            [
-              1488462977.256,
-              '10.140625',
-            ],
-            [
-              1488463037.256,
-              '10.140625',
-            ],
-            [
-              1488463097.256,
-              '10.14453125',
-            ],
-            [
-              1488463157.256,
-              '10.1484375',
-            ],
-            [
-              1488463217.256,
-              '10.15625',
-            ],
-            [
-              1488463277.256,
-              '10.15625',
-            ],
-            [
-              1488463337.256,
-              '10.15625',
-            ],
-            [
-              1488463397.256,
-              '10.1640625',
-            ],
-            [
-              1488463457.256,
-              '10.171875',
-            ],
-            [
-              1488463517.256,
-              '10.171875',
-            ],
-            [
-              1488463577.256,
-              '10.171875',
-            ],
-            [
-              1488463637.256,
-              '10.18359375',
-            ],
-            [
-              1488463697.256,
-              '10.1953125',
-            ],
-            [
-              1488463757.256,
-              '10.203125',
-            ],
-            [
-              1488463817.256,
-              '10.20703125',
-            ],
-            [
-              1488463877.256,
-              '10.20703125',
-            ],
-            [
-              1488463937.256,
-              '10.20703125',
-            ],
-            [
-              1488463997.256,
-              '10.20703125',
-            ],
-            [
-              1488464057.256,
-              '10.2109375',
-            ],
-            [
-              1488464117.256,
-              '10.2109375',
-            ],
-            [
-              1488464177.256,
-              '10.2109375',
-            ],
-            [
-              1488464237.256,
-              '10.2109375',
-            ],
-            [
-              1488464297.256,
-              '10.21484375',
-            ],
-            [
-              1488464357.256,
-              '10.22265625',
-            ],
-            [
-              1488464417.256,
-              '10.22265625',
-            ],
-            [
-              1488464477.256,
-              '10.2265625',
-            ],
-            [
-              1488464537.256,
-              '10.23046875',
-            ],
-            [
-              1488464597.256,
-              '10.23046875',
-            ],
-            [
-              1488464657.256,
-              '10.234375',
-            ],
-            [
-              1488464717.256,
-              '10.234375',
-            ],
-            [
-              1488464777.256,
-              '10.234375',
-            ],
-            [
-              1488464837.256,
-              '10.234375',
-            ],
-            [
-              1488464897.256,
-              '10.234375',
-            ],
-            [
-              1488464957.256,
-              '10.234375',
-            ],
-            [
-              1488465017.256,
-              '10.23828125',
-            ],
-            [
-              1488465077.256,
-              '10.23828125',
-            ],
-            [
-              1488465137.256,
-              '10.2421875',
-            ],
-            [
-              1488465197.256,
-              '10.2421875',
-            ],
-            [
-              1488465257.256,
-              '10.2421875',
-            ],
-            [
-              1488465317.256,
-              '10.2421875',
-            ],
-            [
-              1488465377.256,
-              '10.2421875',
-            ],
-            [
-              1488465437.256,
-              '10.2421875',
-            ],
-            [
-              1488465497.256,
-              '10.2421875',
-            ],
-            [
-              1488465557.256,
-              '10.2421875',
-            ],
-            [
-              1488465617.256,
-              '10.2421875',
-            ],
-            [
-              1488465677.256,
-              '10.2421875',
-            ],
-            [
-              1488465737.256,
-              '10.2421875',
-            ],
-            [
-              1488465797.256,
-              '10.24609375',
-            ],
-            [
-              1488465857.256,
-              '10.25',
-            ],
-            [
-              1488465917.256,
-              '10.25390625',
-            ],
-            [
-              1488465977.256,
-              '9.98828125',
-            ],
-            [
-              1488466037.256,
-              '9.9921875',
-            ],
-            [
-              1488466097.256,
-              '9.9921875',
-            ],
-            [
-              1488466157.256,
-              '9.99609375',
-            ],
-            [
-              1488466217.256,
-              '10',
-            ],
-            [
-              1488466277.256,
-              '10.00390625',
-            ],
-            [
-              1488466337.256,
-              '10.0078125',
-            ],
-            [
-              1488466397.256,
-              '10.01171875',
-            ],
-            [
-              1488466457.256,
-              '10.0234375',
-            ],
-            [
-              1488466517.256,
-              '10.02734375',
-            ],
-            [
-              1488466577.256,
-              '10.02734375',
-            ],
-            [
-              1488466637.256,
-              '10.03125',
-            ],
-            [
-              1488466697.256,
-              '10.03125',
-            ],
-            [
-              1488466757.256,
-              '10.03125',
-            ],
-            [
-              1488466817.256,
-              '10.03125',
-            ],
-            [
-              1488466877.256,
-              '10.03125',
-            ],
-            [
-              1488466937.256,
-              '10.03125',
-            ],
-            [
-              1488466997.256,
-              '10.03125',
-            ],
-            [
-              1488467057.256,
-              '10.0390625',
-            ],
-            [
-              1488467117.256,
-              '10.0390625',
-            ],
-            [
-              1488467177.256,
-              '10.04296875',
-            ],
-            [
-              1488467237.256,
-              '10.05078125',
-            ],
-            [
-              1488467297.256,
-              '10.05859375',
-            ],
-            [
-              1488467357.256,
-              '10.06640625',
-            ],
-            [
-              1488467417.256,
-              '10.06640625',
-            ],
-            [
-              1488467477.256,
-              '10.0703125',
-            ],
-            [
-              1488467537.256,
-              '10.07421875',
-            ],
-            [
-              1488467597.256,
-              '10.0859375',
-            ],
-            [
-              1488467657.256,
-              '10.0859375',
-            ],
-            [
-              1488467717.256,
-              '10.09765625',
-            ],
-            [
-              1488467777.256,
-              '10.1015625',
-            ],
-            [
-              1488467837.256,
-              '10.10546875',
-            ],
-            [
-              1488467897.256,
-              '10.10546875',
-            ],
-            [
-              1488467957.256,
-              '10.125',
-            ],
-            [
-              1488468017.256,
-              '10.13671875',
-            ],
-            [
-              1488468077.256,
-              '10.1484375',
-            ],
-            [
-              1488468137.256,
-              '10.15625',
-            ],
-            [
-              1488468197.256,
-              '10.16796875',
-            ],
-            [
-              1488468257.256,
-              '10.171875',
-            ],
-            [
-              1488468317.256,
-              '10.171875',
-            ],
-            [
-              1488468377.256,
-              '10.171875',
-            ],
-            [
-              1488468437.256,
-              '10.171875',
-            ],
-            [
-              1488468497.256,
-              '10.171875',
-            ],
-            [
-              1488468557.256,
-              '10.171875',
-            ],
-            [
-              1488468617.256,
-              '10.171875',
-            ],
-            [
-              1488468677.256,
-              '10.17578125',
-            ],
-            [
-              1488468737.256,
-              '10.17578125',
-            ],
-            [
-              1488468797.256,
-              '10.265625',
-            ],
-            [
-              1488468857.256,
-              '10.19921875',
-            ],
-            [
-              1488468917.256,
-              '10.19921875',
-            ],
-            [
-              1488468977.256,
-              '10.19921875',
-            ],
-            [
-              1488469037.256,
-              '10.19921875',
-            ],
-            [
-              1488469097.256,
-              '10.19921875',
-            ],
-            [
-              1488469157.256,
-              '10.203125',
-            ],
-            [
-              1488469217.256,
-              '10.43359375',
-            ],
-            [
-              1488469277.256,
-              '10.20703125',
-            ],
-            [
-              1488469337.256,
-              '10.2109375',
-            ],
-            [
-              1488469397.256,
-              '10.22265625',
-            ],
-            [
-              1488469457.256,
-              '10.21484375',
-            ],
-            [
-              1488469517.256,
-              '10.21484375',
-            ],
-            [
-              1488469577.256,
-              '10.21484375',
-            ],
-            [
-              1488469637.256,
-              '10.22265625',
-            ],
-            [
-              1488469697.256,
-              '10.234375',
-            ],
-            [
-              1488469757.256,
-              '10.234375',
-            ],
-            [
-              1488469817.256,
-              '10.234375',
-            ],
-            [
-              1488469877.256,
-              '10.2421875',
-            ],
-            [
-              1488469937.256,
-              '10.25',
-            ],
-            [
-              1488469997.256,
-              '10.25390625',
-            ],
-            [
-              1488470057.256,
-              '10.26171875',
-            ],
-            [
-              1488470117.256,
-              '10.2734375',
-            ],
-          ],
-        },
-      ],
-      memory_current: [
-        {
-          metric: {
-          },
-          value: [
-            1488470117.737,
-            '10.2734375',
-          ],
-        },
-      ],
-      cpu_values: [
-        {
-          metric: {
-          },
-          values: [
-            [
-              1488462918.15,
-              '0.0002996458625058103',
-            ],
-            [
-              1488462978.15,
-              '0.0002652382333333314',
-            ],
-            [
-              1488463038.15,
-              '0.0003485461333333421',
-            ],
-            [
-              1488463098.15,
-              '0.0003420421999999886',
-            ],
-            [
-              1488463158.15,
-              '0.00023107150000001297',
-            ],
-            [
-              1488463218.15,
-              '0.00030463981666664826',
-            ],
-            [
-              1488463278.15,
-              '0.0002477177833333677',
-            ],
-            [
-              1488463338.15,
-              '0.00026936656666665115',
-            ],
-            [
-              1488463398.15,
-              '0.000406264750000022',
-            ],
-            [
-              1488463458.15,
-              '0.00029592802026561453',
-            ],
-            [
-              1488463518.15,
-              '0.00023426999683316343',
-            ],
-            [
-              1488463578.15,
-              '0.0003057080666666915',
-            ],
-            [
-              1488463638.15,
-              '0.0003408470500000149',
-            ],
-            [
-              1488463698.15,
-              '0.00025497336666665166',
-            ],
-            [
-              1488463758.15,
-              '0.0003009282833333534',
-            ],
-            [
-              1488463818.15,
-              '0.0003119383499999924',
-            ],
-            [
-              1488463878.15,
-              '0.00028719019999998705',
-            ],
-            [
-              1488463938.15,
-              '0.000327864749999988',
-            ],
-            [
-              1488463998.15,
-              '0.0002514917333333422',
-            ],
-            [
-              1488464058.15,
-              '0.0003614651166666742',
-            ],
-            [
-              1488464118.15,
-              '0.0003221668000000122',
-            ],
-            [
-              1488464178.15,
-              '0.00023323083333330884',
-            ],
-            [
-              1488464238.15,
-              '0.00028531499475009274',
-            ],
-            [
-              1488464298.15,
-              '0.0002627695294921391',
-            ],
-            [
-              1488464358.15,
-              '0.00027145463333333453',
-            ],
-            [
-              1488464418.15,
-              '0.00025669488333335266',
-            ],
-            [
-              1488464478.15,
-              '0.00022307761666665965',
-            ],
-            [
-              1488464538.15,
-              '0.0003307265833333517',
-            ],
-            [
-              1488464598.15,
-              '0.0002817050666666709',
-            ],
-            [
-              1488464658.15,
-              '0.00022357458333332285',
-            ],
-            [
-              1488464718.15,
-              '0.00032648590000000275',
-            ],
-            [
-              1488464778.15,
-              '0.00028410750000000816',
-            ],
-            [
-              1488464838.15,
-              '0.0003038076999999954',
-            ],
-            [
-              1488464898.15,
-              '0.00037568226666667335',
-            ],
-            [
-              1488464958.15,
-              '0.00020160354999999202',
-            ],
-            [
-              1488465018.15,
-              '0.0003229403333333399',
-            ],
-            [
-              1488465078.15,
-              '0.00033516069999999236',
-            ],
-            [
-              1488465138.15,
-              '0.0003365978333333371',
-            ],
-            [
-              1488465198.15,
-              '0.00020262178333331585',
-            ],
-            [
-              1488465258.15,
-              '0.00040567498333331876',
-            ],
-            [
-              1488465318.15,
-              '0.00029114155000001436',
-            ],
-            [
-              1488465378.15,
-              '0.0002498841000000122',
-            ],
-            [
-              1488465438.15,
-              '0.00027296763333331715',
-            ],
-            [
-              1488465498.15,
-              '0.0002958794000000135',
-            ],
-            [
-              1488465558.15,
-              '0.0002922354666666867',
-            ],
-            [
-              1488465618.15,
-              '0.00034186624999999653',
-            ],
-            [
-              1488465678.15,
-              '0.0003397984166666627',
-            ],
-            [
-              1488465738.15,
-              '0.0002658284166666469',
-            ],
-            [
-              1488465798.15,
-              '0.00026221139999999346',
-            ],
-            [
-              1488465858.15,
-              '0.00029467960000001034',
-            ],
-            [
-              1488465918.15,
-              '0.0002634141333333358',
-            ],
-            [
-              1488465978.15,
-              '0.0003202958333333209',
-            ],
-            [
-              1488466038.15,
-              '0.00037890760000000394',
-            ],
-            [
-              1488466098.15,
-              '0.00023453356666666518',
-            ],
-            [
-              1488466158.15,
-              '0.0002866827333333433',
-            ],
-            [
-              1488466218.15,
-              '0.0003335935499999998',
-            ],
-            [
-              1488466278.15,
-              '0.00022787131666666125',
-            ],
-            [
-              1488466338.15,
-              '0.00033821938333333064',
-            ],
-            [
-              1488466398.15,
-              '0.00029233375000001043',
-            ],
-            [
-              1488466458.15,
-              '0.00026562758333333514',
-            ],
-            [
-              1488466518.15,
-              '0.0003142600999999819',
-            ],
-            [
-              1488466578.15,
-              '0.00027392178333333444',
-            ],
-            [
-              1488466638.15,
-              '0.00028178598333334173',
-            ],
-            [
-              1488466698.15,
-              '0.0002463400666666911',
-            ],
-            [
-              1488466758.15,
-              '0.00040234373333332125',
-            ],
-            [
-              1488466818.15,
-              '0.00023677453333332822',
-            ],
-            [
-              1488466878.15,
-              '0.00030852703333333523',
-            ],
-            [
-              1488466938.15,
-              '0.0003582272833333455',
-            ],
-            [
-              1488466998.15,
-              '0.0002176380833332973',
-            ],
-            [
-              1488467058.15,
-              '0.00026180203333335447',
-            ],
-            [
-              1488467118.15,
-              '0.00027862966666667436',
-            ],
-            [
-              1488467178.15,
-              '0.0002769731166666567',
-            ],
-            [
-              1488467238.15,
-              '0.0002832899166666477',
-            ],
-            [
-              1488467298.15,
-              '0.0003446533500000311',
-            ],
-            [
-              1488467358.15,
-              '0.0002691345999999761',
-            ],
-            [
-              1488467418.15,
-              '0.000284919933333357',
-            ],
-            [
-              1488467478.15,
-              '0.0002396026166666528',
-            ],
-            [
-              1488467538.15,
-              '0.00035625295000002075',
-            ],
-            [
-              1488467598.15,
-              '0.00036759816666664946',
-            ],
-            [
-              1488467658.15,
-              '0.00030326608333333855',
-            ],
-            [
-              1488467718.15,
-              '0.00023584972418043393',
-            ],
-            [
-              1488467778.15,
-              '0.00025744508892115107',
-            ],
-            [
-              1488467838.15,
-              '0.00036737541666663395',
-            ],
-            [
-              1488467898.15,
-              '0.00034325741666666094',
-            ],
-            [
-              1488467958.15,
-              '0.00026390046666667407',
-            ],
-            [
-              1488468018.15,
-              '0.0003302534500000102',
-            ],
-            [
-              1488468078.15,
-              '0.00035243794999999527',
-            ],
-            [
-              1488468138.15,
-              '0.00020149738333333407',
-            ],
-            [
-              1488468198.15,
-              '0.0003183469666666679',
-            ],
-            [
-              1488468258.15,
-              '0.0003835329166666845',
-            ],
-            [
-              1488468318.15,
-              '0.0002485075333333124',
-            ],
-            [
-              1488468378.15,
-              '0.0003011457166666768',
-            ],
-            [
-              1488468438.15,
-              '0.00032242785497684965',
-            ],
-            [
-              1488468498.15,
-              '0.0002659713747457531',
-            ],
-            [
-              1488468558.15,
-              '0.0003476860333333202',
-            ],
-            [
-              1488468618.15,
-              '0.00028336403333334794',
-            ],
-            [
-              1488468678.15,
-              '0.00017132354999998728',
-            ],
-            [
-              1488468738.15,
-              '0.0003001915833333276',
-            ],
-            [
-              1488468798.15,
-              '0.0003025715666666725',
-            ],
-            [
-              1488468858.15,
-              '0.0003012370166666815',
-            ],
-            [
-              1488468918.15,
-              '0.00030203619999997025',
-            ],
-            [
-              1488468978.15,
-              '0.0002804355000000314',
-            ],
-            [
-              1488469038.15,
-              '0.00033194884999998564',
-            ],
-            [
-              1488469098.15,
-              '0.00025201496666665455',
-            ],
-            [
-              1488469158.15,
-              '0.0002777531500000189',
-            ],
-            [
-              1488469218.15,
-              '0.0003314885833333392',
-            ],
-            [
-              1488469278.15,
-              '0.0002234891422095589',
-            ],
-            [
-              1488469338.15,
-              '0.000349117355867791',
-            ],
-            [
-              1488469398.15,
-              '0.0004036731333333303',
-            ],
-            [
-              1488469458.15,
-              '0.00024553911666667835',
-            ],
-            [
-              1488469518.15,
-              '0.0003056456833333184',
-            ],
-            [
-              1488469578.15,
-              '0.0002618737166666681',
-            ],
-            [
-              1488469638.15,
-              '0.00022972643333331414',
-            ],
-            [
-              1488469698.15,
-              '0.0003713522500000307',
-            ],
-            [
-              1488469758.15,
-              '0.00018322576666666515',
-            ],
-            [
-              1488469818.15,
-              '0.00034534762753952466',
-            ],
-            [
-              1488469878.15,
-              '0.00028200510008501677',
-            ],
-            [
-              1488469938.15,
-              '0.0002773708499999768',
-            ],
-            [
-              1488469998.15,
-              '0.00027547160000001013',
-            ],
-            [
-              1488470058.15,
-              '0.00031713610000000023',
-            ],
-            [
-              1488470118.15,
-              '0.00035276853333332525',
-            ],
-          ],
-        },
-      ],
-      cpu_current: [
-        {
-          metric: {
-          },
-          value: [
-            1488470118.566,
-            '0.00035276853333332525',
-          ],
-        },
-      ],
-      last_update: '2017-03-02T15:55:18.981Z',
-    },
-  },
-};
diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js
index c6f218e4dacf697542882d9ec8f1a5f807e1c5fa..2c096ed08a8b09eac217699271a8a741936ad650 100644
--- a/spec/javascripts/notes_spec.js
+++ b/spec/javascripts/notes_spec.js
@@ -176,7 +176,7 @@ import '~/notes';
 
         Notes.updateNoteTargetSelector($note);
 
-        expect($note.toggleClass).toHaveBeenCalledWith('target', null);
+        expect($note.toggleClass).toHaveBeenCalledWith('target', false);
       });
     });
 
@@ -523,6 +523,51 @@ import '~/notes';
       });
     });
 
+    describe('postComment with Slash commands', () => {
+      const sampleComment = '/assign @root\n/award :100:';
+      const note = {
+        commands_changes: {
+          assignee_id: 1,
+          emoji_award: '100'
+        },
+        errors: {
+          commands_only: ['Commands applied']
+        },
+        valid: false
+      };
+      let $form;
+      let $notesContainer;
+
+      beforeEach(() => {
+        this.notes = new Notes('', []);
+        window.gon.current_username = 'root';
+        window.gon.current_user_fullname = 'Administrator';
+        gl.awardsHandler = {
+          addAwardToEmojiBar: () => {},
+          scrollToAwards: () => {}
+        };
+        gl.GfmAutoComplete = {
+          dataSources: {
+            commands: '/root/test-project/autocomplete_sources/commands'
+          }
+        };
+        $form = $('form.js-main-target-form');
+        $notesContainer = $('ul.main-notes-list');
+        $form.find('textarea.js-note-text').val(sampleComment);
+      });
+
+      it('should remove slash command placeholder when comment with slash commands is done posting', () => {
+        const deferred = $.Deferred();
+        spyOn($, 'ajax').and.returnValue(deferred.promise());
+        spyOn(gl.awardsHandler, 'addAwardToEmojiBar').and.callThrough();
+        $('.js-comment-button').click();
+
+        expect($notesContainer.find('.system-note.being-posted').length).toEqual(1); // Placeholder shown
+        deferred.resolve(note);
+        expect($notesContainer.find('.system-note.being-posted').length).toEqual(0); // Placeholder removed
+      });
+    });
+
     describe('update comment with script tags', () => {
       const sampleComment = '<script></script>';
       const updatedComment = '<script></script>';
@@ -595,46 +640,46 @@ import '~/notes';
       });
     });
 
-    describe('hasSlashCommands', () => {
+    describe('hasQuickActions', () => {
       beforeEach(() => {
         this.notes = new Notes('', []);
       });
 
-      it('should return true when comment begins with a slash command', () => {
+      it('should return true when comment begins with a quick action', () => {
         const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign Merging this';
-        const hasSlashCommands = this.notes.hasSlashCommands(sampleComment);
+        const hasQuickActions = this.notes.hasQuickActions(sampleComment);
 
-        expect(hasSlashCommands).toBeTruthy();
+        expect(hasQuickActions).toBeTruthy();
       });
 
-      it('should return false when comment does NOT begin with a slash command', () => {
+      it('should return false when comment does NOT begin with a quick action', () => {
         const sampleComment = 'Hey, /unassign Merging this';
-        const hasSlashCommands = this.notes.hasSlashCommands(sampleComment);
+        const hasQuickActions = this.notes.hasQuickActions(sampleComment);
 
-        expect(hasSlashCommands).toBeFalsy();
+        expect(hasQuickActions).toBeFalsy();
       });
 
-      it('should return false when comment does NOT have any slash commands', () => {
+      it('should return false when comment does NOT have any quick actions', () => {
         const sampleComment = 'Looking good, Awesome!';
-        const hasSlashCommands = this.notes.hasSlashCommands(sampleComment);
+        const hasQuickActions = this.notes.hasQuickActions(sampleComment);
 
-        expect(hasSlashCommands).toBeFalsy();
+        expect(hasQuickActions).toBeFalsy();
       });
     });
 
-    describe('stripSlashCommands', () => {
-      it('should strip slash commands from the comment which begins with a slash command', () => {
+    describe('stripQuickActions', () => {
+      it('should strip quick actions from the comment which begins with a quick action', () => {
         this.notes = new Notes();
         const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign Merging this';
-        const stripedComment = this.notes.stripSlashCommands(sampleComment);
+        const stripedComment = this.notes.stripQuickActions(sampleComment);
 
         expect(stripedComment).toBe('');
       });
 
-      it('should strip slash commands from the comment but leaves plain comment if it is present', () => {
+      it('should strip quick actions from the comment but leaves plain comment if it is present', () => {
         this.notes = new Notes();
         const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign\nMerging this';
-        const stripedComment = this.notes.stripSlashCommands(sampleComment);
+        const stripedComment = this.notes.stripQuickActions(sampleComment);
 
         expect(stripedComment).toBe('Merging this');
       });
@@ -642,14 +687,14 @@ import '~/notes';
       it('should NOT strip string that has slashes within', () => {
         this.notes = new Notes();
         const sampleComment = 'http://127.0.0.1:3000/root/gitlab-shell/issues/1';
-        const stripedComment = this.notes.stripSlashCommands(sampleComment);
+        const stripedComment = this.notes.stripQuickActions(sampleComment);
 
         expect(stripedComment).toBe(sampleComment);
       });
     });
 
-    describe('getSlashCommandDescription', () => {
-      const availableSlashCommands = [
+    describe('getQuickActionDescription', () => {
+      const availableQuickActions = [
         { name: 'close', description: 'Close this issue', params: [] },
         { name: 'title', description: 'Change title', params: [{}] },
         { name: 'estimate', description: 'Set time estimate', params: [{}] }
@@ -659,19 +704,19 @@ import '~/notes';
         this.notes = new Notes();
       });
 
-      it('should return executing slash command description when note has single slash command', () => {
+      it('should return executing quick action description when note has single quick action', () => {
         const sampleComment = '/close';
-        expect(this.notes.getSlashCommandDescription(sampleComment, availableSlashCommands)).toBe('Applying command to close this issue');
+        expect(this.notes.getQuickActionDescription(sampleComment, availableQuickActions)).toBe('Applying command to close this issue');
       });
 
-      it('should return generic multiple slash command description when note has multiple slash commands', () => {
+      it('should return generic multiple quick action description when note has multiple quick actions', () => {
         const sampleComment = '/close\n/title [Duplicate] Issue foobar';
-        expect(this.notes.getSlashCommandDescription(sampleComment, availableSlashCommands)).toBe('Applying multiple commands');
+        expect(this.notes.getQuickActionDescription(sampleComment, availableQuickActions)).toBe('Applying multiple commands');
       });
 
-      it('should return generic slash command description when available slash commands list is not populated', () => {
+      it('should return generic quick action description when available quick actions list is not populated', () => {
         const sampleComment = '/close\n/title [Duplicate] Issue foobar';
-        expect(this.notes.getSlashCommandDescription(sampleComment)).toBe('Applying command');
+        expect(this.notes.getQuickActionDescription(sampleComment)).toBe('Applying command');
       });
     });
 
diff --git a/spec/javascripts/pipeline_schedules/interval_pattern_input_spec.js b/spec/javascripts/pipeline_schedules/interval_pattern_input_spec.js
index 56c57d94798a3f5c50ca2f968ccd1884af708b83..040d14efed246193b8fda7b70419ba63e061f54b 100644
--- a/spec/javascripts/pipeline_schedules/interval_pattern_input_spec.js
+++ b/spec/javascripts/pipeline_schedules/interval_pattern_input_spec.js
@@ -1,5 +1,8 @@
 import Vue from 'vue';
-import IntervalPatternInput from '~/pipeline_schedules/components/interval_pattern_input';
+import Translate from '~/vue_shared/translate';
+import IntervalPatternInput from '~/pipeline_schedules/components/interval_pattern_input.vue';
+
+Vue.use(Translate);
 
 const IntervalPatternInputComponent = Vue.extend(IntervalPatternInput);
 const inputNameAttribute = 'schedule[cron]';
diff --git a/spec/javascripts/pipelines/async_button_spec.js b/spec/javascripts/pipelines/async_button_spec.js
index 28c9c7ab2829c800e6adfa7101da62641702e690..486208983579869ff87b631928dae01e4aa6eec3 100644
--- a/spec/javascripts/pipelines/async_button_spec.js
+++ b/spec/javascripts/pipelines/async_button_spec.js
@@ -1,25 +1,20 @@
 import Vue from 'vue';
 import asyncButtonComp from '~/pipelines/components/async_button.vue';
+import eventHub from '~/pipelines/event_hub';
 
 describe('Pipelines Async Button', () => {
   let component;
-  let spy;
   let AsyncButtonComponent;
 
   beforeEach(() => {
     AsyncButtonComponent = Vue.extend(asyncButtonComp);
 
-    spy = jasmine.createSpy('spy').and.returnValue(Promise.resolve());
-
     component = new AsyncButtonComponent({
       propsData: {
         endpoint: '/foo',
         title: 'Foo',
         icon: 'fa fa-foo',
         cssClass: 'bar',
-        service: {
-          postAction: spy,
-        },
       },
     }).$mount();
   });
@@ -33,7 +28,7 @@ describe('Pipelines Async Button', () => {
   });
 
   it('should render the provided title', () => {
-    expect(component.$el.getAttribute('title')).toContain('Foo');
+    expect(component.$el.getAttribute('data-original-title')).toContain('Foo');
     expect(component.$el.getAttribute('aria-label')).toContain('Foo');
   });
 
@@ -41,37 +36,12 @@ describe('Pipelines Async Button', () => {
     expect(component.$el.getAttribute('class')).toContain('bar');
   });
 
-  it('should call the service when it is clicked with the provided endpoint', () => {
-    component.$el.click();
-    expect(spy).toHaveBeenCalledWith('/foo');
-  });
-
-  it('should hide loading if request fails', () => {
-    spy = jasmine.createSpy('spy').and.returnValue(Promise.reject());
-
-    component = new AsyncButtonComponent({
-      propsData: {
-        endpoint: '/foo',
-        title: 'Foo',
-        icon: 'fa fa-foo',
-        cssClass: 'bar',
-        dataAttributes: {
-          'data-foo': 'foo',
-        },
-        service: {
-          postAction: spy,
-        },
-      },
-    }).$mount();
-
-    component.$el.click();
-    expect(component.$el.querySelector('.fa-spinner')).toBe(null);
-  });
-
   describe('With confirm dialog', () => {
     it('should call the service when confimation is positive', () => {
       spyOn(window, 'confirm').and.returnValue(true);
-      spy = jasmine.createSpy('spy').and.returnValue(Promise.resolve());
+      eventHub.$on('postAction', (endpoint) => {
+        expect(endpoint).toEqual('/foo');
+      });
 
       component = new AsyncButtonComponent({
         propsData: {
@@ -79,15 +49,11 @@ describe('Pipelines Async Button', () => {
           title: 'Foo',
           icon: 'fa fa-foo',
           cssClass: 'bar',
-          service: {
-            postAction: spy,
-          },
           confirmActionMessage: 'bar',
         },
       }).$mount();
 
       component.$el.click();
-      expect(spy).toHaveBeenCalledWith('/foo');
     });
   });
 });
diff --git a/spec/javascripts/pipelines/pipelines_actions_spec.js b/spec/javascripts/pipelines/pipelines_actions_spec.js
index 8a58b77f1e3e25b441fcb7486e45e05b68f8d9cf..72fb0a8f9efc52536cbcf154e83363419f60fde1 100644
--- a/spec/javascripts/pipelines/pipelines_actions_spec.js
+++ b/spec/javascripts/pipelines/pipelines_actions_spec.js
@@ -3,7 +3,6 @@ import pipelinesActionsComp from '~/pipelines/components/pipelines_actions.vue';
 
 describe('Pipelines Actions dropdown', () => {
   let component;
-  let spy;
   let actions;
   let ActionsComponent;
 
@@ -22,14 +21,9 @@ describe('Pipelines Actions dropdown', () => {
       },
     ];
 
-    spy = jasmine.createSpy('spy').and.returnValue(Promise.resolve());
-
     component = new ActionsComponent({
       propsData: {
         actions,
-        service: {
-          postAction: spy,
-        },
       },
     }).$mount();
   });
@@ -40,31 +34,6 @@ describe('Pipelines Actions dropdown', () => {
     ).toEqual(actions.length);
   });
 
-  it('should call the service when an action is clicked', () => {
-    component.$el.querySelector('.js-pipeline-dropdown-manual-actions').click();
-    component.$el.querySelector('.js-pipeline-action-link').click();
-
-    expect(spy).toHaveBeenCalledWith(actions[0].path);
-  });
-
-  it('should hide loading if request fails', () => {
-    spy = jasmine.createSpy('spy').and.returnValue(Promise.reject());
-
-    component = new ActionsComponent({
-      propsData: {
-        actions,
-        service: {
-          postAction: spy,
-        },
-      },
-    }).$mount();
-
-    component.$el.querySelector('.js-pipeline-dropdown-manual-actions').click();
-    component.$el.querySelector('.js-pipeline-action-link').click();
-
-    expect(component.$el.querySelector('.fa-spinner')).toEqual(null);
-  });
-
   it('should render a disabled action when it\'s not playable', () => {
     expect(
       component.$el.querySelector('.dropdown-menu li:last-child button').getAttribute('disabled'),
diff --git a/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js b/spec/javascripts/pipelines/pipelines_table_row_spec.js
similarity index 98%
rename from spec/javascripts/vue_shared/components/pipelines_table_row_spec.js
rename to spec/javascripts/pipelines/pipelines_table_row_spec.js
index 9475ee28a03e2d6c3b92a4bb2fcd389b44bb739d..7ce39dca112432126451df3bd014c637d694831a 100644
--- a/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js
+++ b/spec/javascripts/pipelines/pipelines_table_row_spec.js
@@ -1,5 +1,5 @@
 import Vue from 'vue';
-import tableRowComp from '~/vue_shared/components/pipelines_table_row.vue';
+import tableRowComp from '~/pipelines/components/pipelines_table_row.vue';
 
 describe('Pipelines Table Row', () => {
   const jsonFixtureName = 'pipelines/pipelines.json';
diff --git a/spec/javascripts/vue_shared/components/pipelines_table_spec.js b/spec/javascripts/pipelines/pipelines_table_spec.js
similarity index 90%
rename from spec/javascripts/vue_shared/components/pipelines_table_spec.js
rename to spec/javascripts/pipelines/pipelines_table_spec.js
index 4c35d702004af85d93488a6a1eaaed04ba100e2c..3afe89c8db4d61ed96a6f1dfc9b15f23fa97298c 100644
--- a/spec/javascripts/vue_shared/components/pipelines_table_spec.js
+++ b/spec/javascripts/pipelines/pipelines_table_spec.js
@@ -1,5 +1,5 @@
 import Vue from 'vue';
-import pipelinesTableComp from '~/vue_shared/components/pipelines_table.vue';
+import pipelinesTableComp from '~/pipelines/components/pipelines_table.vue';
 import '~/lib/utils/datetime_utility';
 
 describe('Pipelines Table', () => {
@@ -22,7 +22,6 @@ describe('Pipelines Table', () => {
       component = new PipelinesTableComponent({
         propsData: {
           pipelines: [],
-          service: {},
         },
       }).$mount();
     });
@@ -48,7 +47,6 @@ describe('Pipelines Table', () => {
       const component = new PipelinesTableComponent({
         propsData: {
           pipelines: [],
-          service: {},
         },
       }).$mount();
       expect(component.$el.querySelectorAll('.commit.gl-responsive-table-row').length).toEqual(0);
@@ -58,10 +56,8 @@ describe('Pipelines Table', () => {
   describe('with data', () => {
     it('should render rows', () => {
       const component = new PipelinesTableComponent({
-        el: document.querySelector('.test-dom-element'),
         propsData: {
           pipelines: [pipeline],
-          service: {},
         },
       }).$mount();
 
diff --git a/spec/javascripts/pipelines/stage_spec.js b/spec/javascripts/pipelines/stage_spec.js
index a4f32a1faed20bc5919abae8b0d7f0440fc20cd8..1b96b2e3d51c04e665bcc928c9d80336f2ef3b2c 100644
--- a/spec/javascripts/pipelines/stage_spec.js
+++ b/spec/javascripts/pipelines/stage_spec.js
@@ -83,4 +83,47 @@ describe('Pipelines stage component', () => {
       }, 0);
     });
   });
+
+  describe('update endpoint correctly', () => {
+    const updatedInterceptor = (request, next) => {
+      if (request.url === 'bar') {
+        next(request.respondWith(JSON.stringify({ html: 'this is the updated content' }), {
+          status: 200,
+        }));
+      }
+      next();
+    };
+
+    beforeEach(() => {
+      Vue.http.interceptors.push(updatedInterceptor);
+    });
+
+    afterEach(() => {
+      Vue.http.interceptors = _.without(
+        Vue.http.interceptors, updatedInterceptor,
+      );
+    });
+
+    it('should update the stage to request the new endpoint provided', (done) => {
+      component.stage = {
+        status: {
+          group: 'running',
+          icon: 'running',
+          title: 'running',
+        },
+        dropdown_path: 'bar',
+      };
+
+      Vue.nextTick(() => {
+        component.$el.querySelector('button').click();
+
+        setTimeout(() => {
+          expect(
+            component.$el.querySelector('.js-builds-dropdown-container ul').textContent.trim(),
+            ).toEqual('this is the updated content');
+          done();
+        });
+      });
+    });
+  });
 });
diff --git a/spec/javascripts/project_title_spec.js b/spec/javascripts/project_title_spec.js
index 3dba2e817ffcb10d1cb7c1d0727bf07c87aa81ac..cc336180ff77fd90c400e4eae850a6f9c0e20e3b 100644
--- a/spec/javascripts/project_title_spec.js
+++ b/spec/javascripts/project_title_spec.js
@@ -1,4 +1,3 @@
-/* eslint-disable space-before-function-paren, no-unused-expressions, no-return-assign, no-param-reassign, no-var, new-cap, wrap-iife, no-unused-vars, quotes, jasmine/no-expect-in-setup-teardown, max-len */
 /* global Project */
 
 import 'select2/select2';
@@ -7,47 +6,52 @@ import '~/api';
 import '~/project_select';
 import '~/project';
 
-(function() {
-  describe('Project Title', function() {
-    preloadFixtures('issues/open-issue.html.raw');
-    loadJSONFixtures('projects.json');
+describe('Project Title', () => {
+  preloadFixtures('issues/open-issue.html.raw');
+  loadJSONFixtures('projects.json');
 
-    beforeEach(function() {
-      loadFixtures('issues/open-issue.html.raw');
+  beforeEach(() => {
+    loadFixtures('issues/open-issue.html.raw');
 
-      window.gon = {};
-      window.gon.api_version = 'v3';
+    window.gon = {};
+    window.gon.api_version = 'v3';
 
-      return this.project = new Project();
-    });
+    // eslint-disable-next-line no-new
+    new Project();
+  });
 
-    describe('project list', function() {
-      var fakeAjaxResponse = function fakeAjaxResponse(req) {
-        var d;
-        expect(req.url).toBe('/api/v3/projects.json?simple=true');
-        expect(req.data).toEqual({ search: '', order_by: 'last_activity_at', per_page: 20, membership: true });
-        d = $.Deferred();
-        d.resolve(this.projects_data);
-        return d.promise();
-      };
-
-      beforeEach((function(_this) {
-        return function() {
-          _this.projects_data = getJSONFixture('projects.json');
-          return spyOn(jQuery, 'ajax').and.callFake(fakeAjaxResponse.bind(_this));
-        };
-      })(this));
-      it('toggles dropdown', function() {
-        var menu = $('.js-dropdown-menu-projects');
-        $('.js-projects-dropdown-toggle').click();
-        expect(menu).toHaveClass('open');
-        menu.find('.dropdown-menu-close-icon').click();
-        expect(menu).not.toHaveClass('open');
+  describe('project list', () => {
+    let reqUrl;
+    let reqData;
+
+    beforeEach(() => {
+      const fakeResponseData = getJSONFixture('projects.json');
+      spyOn(jQuery, 'ajax').and.callFake((req) => {
+        const def = $.Deferred();
+        reqUrl = req.url;
+        reqData = req.data;
+        def.resolve(fakeResponseData);
+        return def.promise();
       });
     });
 
-    afterEach(() => {
-      window.gon = {};
+    it('toggles dropdown', () => {
+      const $menu = $('.js-dropdown-menu-projects');
+      $('.js-projects-dropdown-toggle').click();
+      expect($menu).toHaveClass('open');
+      expect(reqUrl).toBe('/api/v3/projects.json?simple=true');
+      expect(reqData).toEqual({
+        search: '',
+        order_by: 'last_activity_at',
+        per_page: 20,
+        membership: true,
+      });
+      $menu.find('.dropdown-menu-close-icon').click();
+      expect($menu).not.toHaveClass('open');
     });
   });
-}).call(window);
+
+  afterEach(() => {
+    window.gon = {};
+  });
+});
diff --git a/spec/javascripts/prometheus_metrics/mock_data.js b/spec/javascripts/prometheus_metrics/mock_data.js
new file mode 100644
index 0000000000000000000000000000000000000000..3af56df92e2c3228b868b3a3ef0dcd946c7ce2a8
--- /dev/null
+++ b/spec/javascripts/prometheus_metrics/mock_data.js
@@ -0,0 +1,41 @@
+export const metrics = [
+  {
+    group: 'Kubernetes',
+    priority: 1,
+    active_metrics: 4,
+    metrics_missing_requirements: 0,
+  },
+  {
+    group: 'HAProxy',
+    priority: 2,
+    active_metrics: 3,
+    metrics_missing_requirements: 0,
+  },
+  {
+    group: 'Apache',
+    priority: 3,
+    active_metrics: 5,
+    metrics_missing_requirements: 0,
+  },
+];
+
+export const missingVarMetrics = [
+  {
+    group: 'Kubernetes',
+    priority: 1,
+    active_metrics: 4,
+    metrics_missing_requirements: 0,
+  },
+  {
+    group: 'HAProxy',
+    priority: 2,
+    active_metrics: 3,
+    metrics_missing_requirements: 1,
+  },
+  {
+    group: 'Apache',
+    priority: 3,
+    active_metrics: 5,
+    metrics_missing_requirements: 3,
+  },
+];
diff --git a/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js b/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..2b3a821dbd9bafd49a41f724596662923b9545b1
--- /dev/null
+++ b/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js
@@ -0,0 +1,158 @@
+import PrometheusMetrics from '~/prometheus_metrics/prometheus_metrics';
+import PANEL_STATE from '~/prometheus_metrics/constants';
+import { metrics, missingVarMetrics } from './mock_data';
+
+describe('PrometheusMetrics', () => {
+  const FIXTURE = 'services/prometheus/prometheus_service.html.raw';
+  preloadFixtures(FIXTURE);
+
+  beforeEach(() => {
+    loadFixtures(FIXTURE);
+  });
+
+  describe('constructor', () => {
+    let prometheusMetrics;
+
+    beforeEach(() => {
+      prometheusMetrics = new PrometheusMetrics('.js-prometheus-metrics-monitoring');
+    });
+
+    it('should initialize wrapper element refs on class object', () => {
+      expect(prometheusMetrics.$wrapper).toBeDefined();
+      expect(prometheusMetrics.$monitoredMetricsPanel).toBeDefined();
+      expect(prometheusMetrics.$monitoredMetricsCount).toBeDefined();
+      expect(prometheusMetrics.$monitoredMetricsLoading).toBeDefined();
+      expect(prometheusMetrics.$monitoredMetricsEmpty).toBeDefined();
+      expect(prometheusMetrics.$monitoredMetricsList).toBeDefined();
+      expect(prometheusMetrics.$missingEnvVarPanel).toBeDefined();
+      expect(prometheusMetrics.$panelToggle).toBeDefined();
+      expect(prometheusMetrics.$missingEnvVarMetricCount).toBeDefined();
+      expect(prometheusMetrics.$missingEnvVarMetricsList).toBeDefined();
+    });
+
+    it('should initialize metadata on class object', () => {
+      expect(prometheusMetrics.backOffRequestCounter).toEqual(0);
+      expect(prometheusMetrics.activeMetricsEndpoint).toContain('/test');
+    });
+  });
+
+  describe('showMonitoringMetricsPanelState', () => {
+    let prometheusMetrics;
+
+    beforeEach(() => {
+      prometheusMetrics = new PrometheusMetrics('.js-prometheus-metrics-monitoring');
+    });
+
+    it('should show loading state when called with `loading`', () => {
+      prometheusMetrics.showMonitoringMetricsPanelState(PANEL_STATE.LOADING);
+
+      expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeFalsy();
+      expect(prometheusMetrics.$monitoredMetricsEmpty.hasClass('hidden')).toBeTruthy();
+      expect(prometheusMetrics.$monitoredMetricsList.hasClass('hidden')).toBeTruthy();
+    });
+
+    it('should show metrics list when called with `list`', () => {
+      prometheusMetrics.showMonitoringMetricsPanelState(PANEL_STATE.LIST);
+
+      expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeTruthy();
+      expect(prometheusMetrics.$monitoredMetricsEmpty.hasClass('hidden')).toBeTruthy();
+      expect(prometheusMetrics.$monitoredMetricsList.hasClass('hidden')).toBeFalsy();
+    });
+
+    it('should show empty state when called with `empty`', () => {
+      prometheusMetrics.showMonitoringMetricsPanelState(PANEL_STATE.EMPTY);
+
+      expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeTruthy();
+      expect(prometheusMetrics.$monitoredMetricsEmpty.hasClass('hidden')).toBeFalsy();
+      expect(prometheusMetrics.$monitoredMetricsList.hasClass('hidden')).toBeTruthy();
+    });
+  });
+
+  describe('populateActiveMetrics', () => {
+    let prometheusMetrics;
+
+    beforeEach(() => {
+      prometheusMetrics = new PrometheusMetrics('.js-prometheus-metrics-monitoring');
+    });
+
+    it('should show monitored metrics list', () => {
+      prometheusMetrics.populateActiveMetrics(metrics);
+
+      const $metricsListLi = prometheusMetrics.$monitoredMetricsList.find('li');
+
+      expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeTruthy();
+      expect(prometheusMetrics.$monitoredMetricsList.hasClass('hidden')).toBeFalsy();
+
+      expect(prometheusMetrics.$monitoredMetricsCount.text()).toEqual('12');
+      expect($metricsListLi.length).toEqual(metrics.length);
+      expect($metricsListLi.first().find('.badge').text()).toEqual(`${metrics[0].active_metrics}`);
+    });
+
+    it('should show missing environment variables list', () => {
+      prometheusMetrics.populateActiveMetrics(missingVarMetrics);
+
+      expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeTruthy();
+      expect(prometheusMetrics.$missingEnvVarPanel.hasClass('hidden')).toBeFalsy();
+
+      expect(prometheusMetrics.$missingEnvVarMetricCount.text()).toEqual('2');
+      expect(prometheusMetrics.$missingEnvVarPanel.find('li').length).toEqual(2);
+      expect(prometheusMetrics.$missingEnvVarPanel.find('.flash-container')).toBeDefined();
+    });
+  });
+
+  describe('loadActiveMetrics', () => {
+    let prometheusMetrics;
+
+    beforeEach(() => {
+      prometheusMetrics = new PrometheusMetrics('.js-prometheus-metrics-monitoring');
+    });
+
+    it('should show loader animation while response is being loaded and hide it when request is complete', (done) => {
+      const deferred = $.Deferred();
+      spyOn($, 'getJSON').and.returnValue(deferred.promise());
+
+      prometheusMetrics.loadActiveMetrics();
+
+      expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeFalsy();
+      expect($.getJSON).toHaveBeenCalledWith(prometheusMetrics.activeMetricsEndpoint);
+
+      deferred.resolve({ data: metrics, success: true });
+
+      setTimeout(() => {
+        expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeTruthy();
+        done();
+      });
+    });
+
+    it('should show empty state if response failed to load', (done) => {
+      const deferred = $.Deferred();
+      spyOn($, 'getJSON').and.returnValue(deferred.promise());
+      spyOn(prometheusMetrics, 'populateActiveMetrics');
+
+      prometheusMetrics.loadActiveMetrics();
+
+      deferred.reject();
+
+      setTimeout(() => {
+        expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeTruthy();
+        expect(prometheusMetrics.$monitoredMetricsEmpty.hasClass('hidden')).toBeFalsy();
+        done();
+      });
+    });
+
+    it('should populate metrics list once response is loaded', (done) => {
+      const deferred = $.Deferred();
+      spyOn($, 'getJSON').and.returnValue(deferred.promise());
+      spyOn(prometheusMetrics, 'populateActiveMetrics');
+
+      prometheusMetrics.loadActiveMetrics();
+
+      deferred.resolve({ data: metrics, success: true });
+
+      setTimeout(() => {
+        expect(prometheusMetrics.populateActiveMetrics).toHaveBeenCalledWith(metrics);
+        done();
+      });
+    });
+  });
+});
diff --git a/spec/javascripts/sidebar/assignee_title_spec.js b/spec/javascripts/sidebar/assignee_title_spec.js
index 5b5b1bf4140101293e5728e17e59b362ab81dba7..ac93f918ce436a56f31edf221e857c340ac9054a 100644
--- a/spec/javascripts/sidebar/assignee_title_spec.js
+++ b/spec/javascripts/sidebar/assignee_title_spec.js
@@ -33,6 +33,31 @@ describe('AssigneeTitle component', () => {
     });
   });
 
+  describe('gutter toggle', () => {
+    it('does not show toggle by default', () => {
+      component = new AssigneeTitleComponent({
+        propsData: {
+          numberOfAssignees: 2,
+          editable: false,
+        },
+      }).$mount();
+
+      expect(component.$el.querySelector('.gutter-toggle')).toBeNull();
+    });
+
+    it('shows toggle when showToggle is true', () => {
+      component = new AssigneeTitleComponent({
+        propsData: {
+          numberOfAssignees: 2,
+          editable: false,
+          showToggle: true,
+        },
+      }).$mount();
+
+      expect(component.$el.querySelector('.gutter-toggle')).toEqual(jasmine.any(Object));
+    });
+  });
+
   it('does not render spinner by default', () => {
     component = new AssigneeTitleComponent({
       propsData: {
diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js
index 2c34402576bb7497131c0257ef1a79544b7a5100..d4e134583c7b39b403b41c6bc98bd3a346a27e02 100644
--- a/spec/javascripts/test_bundle.js
+++ b/spec/javascripts/test_bundle.js
@@ -1,8 +1,18 @@
+/* eslint-disable jasmine/no-global-setup */
 import $ from 'jquery';
 import _ from 'underscore';
 import 'jasmine-jquery';
 import '~/commons';
 
+import Vue from 'vue';
+import VueResource from 'vue-resource';
+
+const isHeadlessChrome = /\bHeadlessChrome\//.test(navigator.userAgent);
+Vue.config.devtools = !isHeadlessChrome;
+Vue.config.productionTip = false;
+
+Vue.use(VueResource);
+
 // enable test fixtures
 jasmine.getFixtures().fixturesPath = '/base/spec/javascripts/fixtures';
 jasmine.getJSONFixtures().fixturesPath = '/base/spec/javascripts/fixtures';
@@ -16,6 +26,45 @@ window.gl = window.gl || {};
 window.gl.TEST_HOST = 'http://test.host';
 window.gon = window.gon || {};
 
+let hasUnhandledPromiseRejections = false;
+
+window.addEventListener('unhandledrejection', (event) => {
+  hasUnhandledPromiseRejections = true;
+  console.error('Unhandled promise rejection:');
+  console.error(event.reason.stack || event.reason);
+});
+
+const checkUnhandledPromiseRejections = (done) => {
+  expect(hasUnhandledPromiseRejections).toBe(false);
+  done();
+};
+
+// HACK: Chrome 59 disconnects if there are too many synchronous tests in a row
+// because it appears to lock up the thread that communicates to Karma's socket
+// This async beforeEach gets called on every spec and releases the JS thread long
+// enough for the socket to continue to communicate.
+// The downside is that it creates a minor performance penalty in the time it takes
+// to run our unit tests.
+beforeEach(done => done());
+
+beforeAll(() => {
+  const origError = console.error;
+  spyOn(console, 'error').and.callFake((message) => {
+    if (/^\[Vue warn\]/.test(message)) {
+      fail(message);
+    } else {
+      origError(message);
+    }
+  });
+});
+
+const builtinVueHttpInterceptors = Vue.http.interceptors.slice();
+
+beforeEach(() => {
+  // restore interceptors so we have no remaining ones from previous tests
+  Vue.http.interceptors = builtinVueHttpInterceptors.slice();
+});
+
 // render all of our tests
 const testsContext = require.context('.', true, /_spec$/);
 testsContext.keys().forEach(function (path) {
@@ -31,6 +80,10 @@ testsContext.keys().forEach(function (path) {
   }
 });
 
+it('has no unhandled Promise rejections', (done) => {
+  setTimeout(checkUnhandledPromiseRejections(done), 1000);
+});
+
 // if we're generating coverage reports, make sure to include all files so
 // that we can catch files with 0% coverage
 // see: https://github.com/deepsweet/istanbul-instrumenter-loader/issues/15
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js
index 647b59520f8a1779ba35072082dbc03fe7efe0d8..4b6f171c8d6daffccb5abace8f7134e3c80ed785 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js
@@ -76,6 +76,28 @@ describe('MRWidgetPipeline', () => {
       el = vm.$el;
     });
 
+    afterEach(() => {
+      vm.$destroy();
+    });
+
+    describe('without a pipeline', () => {
+      beforeEach(() => {
+        vm.mr = { pipeline: null };
+      });
+
+      it('should render message with spinner', (done) => {
+        Vue.nextTick()
+          .then(() => {
+            expect(el.querySelector('.pipeline-id')).toBe(null);
+            expect(el.innerText.trim()).toBe('Waiting for pipeline...');
+            expect(el.querySelectorAll('i.fa.fa-spinner.fa-spin').length).toBe(1);
+            done();
+          })
+          .then(done)
+          .catch(done.fail);
+      });
+    });
+
     it('should render template elements correctly', () => {
       expect(el.classList.contains('mr-widget-heading')).toBeTruthy();
       expect(el.querySelectorAll('.ci-status-icon.ci-status-icon-success').length).toEqual(1);
@@ -93,39 +115,47 @@ describe('MRWidgetPipeline', () => {
     it('should list single stage', (done) => {
       pipeline.details.stages.splice(0, 1);
 
-      Vue.nextTick(() => {
-        expect(el.querySelectorAll('.stage-container button').length).toEqual(1);
-        expect(el.innerText).toContain('with stage');
-        done();
-      });
+      Vue.nextTick()
+        .then(() => {
+          expect(el.querySelectorAll('.stage-container button').length).toEqual(1);
+          expect(el.innerText).toContain('with stage');
+        })
+        .then(done)
+        .catch(done.fail);
     });
 
     it('should not have stages when there is no stage', (done) => {
       vm.mr.pipeline.details.stages = [];
 
-      Vue.nextTick(() => {
-        expect(el.querySelectorAll('.stage-container button').length).toEqual(0);
-        done();
-      });
+      Vue.nextTick()
+        .then(() => {
+          expect(el.querySelectorAll('.stage-container button').length).toEqual(0);
+        })
+        .then(done)
+        .catch(done.fail);
     });
 
     it('should not have coverage text when pipeline has no coverage info', (done) => {
       vm.mr.pipeline.coverage = null;
 
-      Vue.nextTick(() => {
-        expect(el.querySelector('.js-mr-coverage')).toEqual(null);
-        done();
-      });
+      Vue.nextTick()
+        .then(() => {
+          expect(el.querySelector('.js-mr-coverage')).toEqual(null);
+        })
+        .then(done)
+        .catch(done.fail);
     });
 
     it('should show CI error when there is a CI error', (done) => {
       vm.mr.ciStatus = null;
 
-      Vue.nextTick(() => {
-        expect(el.querySelectorAll('.js-ci-error').length).toEqual(1);
-        expect(el.innerText).toContain('Could not connect to the CI server');
-        done();
-      });
+      Vue.nextTick()
+        .then(() => {
+          expect(el.querySelectorAll('.js-ci-error').length).toEqual(1);
+          expect(el.innerText).toContain('Could not connect to the CI server');
+        })
+        .then(done)
+        .catch(done.fail);
     });
   });
 });
diff --git a/spec/javascripts/vue_shared/components/markdown/field_spec.js b/spec/javascripts/vue_shared/components/markdown/field_spec.js
index 4bbaff561fcada4b98195c37add786dbd57f2ed7..291e19c9f3c625a3aabf946e50a0467a6dc8d739 100644
--- a/spec/javascripts/vue_shared/components/markdown/field_spec.js
+++ b/spec/javascripts/vue_shared/components/markdown/field_spec.js
@@ -4,47 +4,33 @@ import fieldComponent from '~/vue_shared/components/markdown/field.vue';
 describe('Markdown field component', () => {
   let vm;
 
-  beforeEach(() => {
+  beforeEach((done) => {
     vm = new Vue({
-      render(createElement) {
-        return createElement(
-          fieldComponent,
-          {
-            props: {
-              markdownPreviewUrl: '/preview',
-              markdownDocs: '/docs',
-            },
-          },
-          [
-            createElement('textarea', {
-              slot: 'textarea',
-            }),
-          ],
-        );
+      data() {
+        return {
+          text: 'testing\n123',
+        };
       },
-    });
-  });
-
-  it('creates a new instance of GL form', (done) => {
-    spyOn(gl, 'GLForm');
-    vm.$mount();
-
-    Vue.nextTick(() => {
-      expect(
-        gl.GLForm,
-      ).toHaveBeenCalled();
-
-      done();
-    });
+      components: {
+        fieldComponent,
+      },
+      template: `
+        <field-component
+          marodown-preview-url="/preview"
+          markdown-docs="/docs"
+        >
+          <textarea
+            slot="textarea"
+            v-model="text">
+          </textarea>
+        </field-component>
+      `,
+    }).$mount();
+
+    Vue.nextTick(done);
   });
 
   describe('mounted', () => {
-    beforeEach((done) => {
-      vm.$mount();
-
-      Vue.nextTick(done);
-    });
-
     it('renders textarea inside backdrop', () => {
       expect(
         vm.$el.querySelector('.zen-backdrop textarea'),
@@ -117,5 +103,52 @@ describe('Markdown field component', () => {
         });
       });
     });
+
+    describe('markdown buttons', () => {
+      it('converts single words', (done) => {
+        const textarea = vm.$el.querySelector('textarea');
+
+        textarea.setSelectionRange(0, 7);
+        vm.$el.querySelector('.js-md').click();
+
+        Vue.nextTick(() => {
+          expect(
+            textarea.value,
+          ).toContain('**testing**');
+
+          done();
+        });
+      });
+
+      it('converts a line', (done) => {
+        const textarea = vm.$el.querySelector('textarea');
+
+        textarea.setSelectionRange(0, 0);
+        vm.$el.querySelectorAll('.js-md')[4].click();
+
+        Vue.nextTick(() => {
+          expect(
+            textarea.value,
+          ).toContain('*  testing');
+
+          done();
+        });
+      });
+
+      it('converts multiple lines', (done) => {
+        const textarea = vm.$el.querySelector('textarea');
+
+        textarea.setSelectionRange(0, 50);
+        vm.$el.querySelectorAll('.js-md')[4].click();
+
+        Vue.nextTick(() => {
+          expect(
+            textarea.value,
+          ).toContain('* testing\n* 123');
+
+          done();
+        });
+      });
+    });
   });
 });
diff --git a/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js b/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js
index f3b4adc0b702f79e6465fcdcd783c5e4ce715a32..b4c1f70ed1e77bd7627c1663c884a9471ebf93d0 100644
--- a/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js
+++ b/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js
@@ -22,7 +22,6 @@ describe('Time ago with tooltip component', () => {
     }).$mount();
 
     expect(vm.$el.tagName).toEqual('TIME');
-    expect(vm.$el.classList.contains('js-vue-timeago')).toEqual(true);
     expect(
       vm.$el.getAttribute('data-original-title'),
     ).toEqual(gl.utils.formatDate('2017-05-08T14:57:39.781Z'));
diff --git a/spec/javascripts/vue_shared/directives/tooltip_spec.js b/spec/javascripts/vue_shared/directives/tooltip_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..b1b3071527b2240268543df8c16c7e06b8b54da2
--- /dev/null
+++ b/spec/javascripts/vue_shared/directives/tooltip_spec.js
@@ -0,0 +1,63 @@
+import Vue from 'vue';
+import tooltip from '~/vue_shared/directives/tooltip';
+
+describe('Tooltip directive', () => {
+  let vm;
+
+  afterEach(() => {
+    if (vm) {
+      vm.$destroy();
+    }
+  });
+
+  describe('with a single tooltip', () => {
+    beforeEach(() => {
+      const SomeComponent = Vue.extend({
+        directives: {
+          tooltip,
+        },
+        template: `
+          <div
+            v-tooltip
+            title="foo">
+          </div>
+        `,
+      });
+
+      vm = new SomeComponent().$mount();
+    });
+
+    it('should have tooltip plugin applied', () => {
+      expect($(vm.$el).data('bs.tooltip')).toBeDefined();
+    });
+  });
+
+  describe('with multiple tooltips', () => {
+    beforeEach(() => {
+      const SomeComponent = Vue.extend({
+        directives: {
+          tooltip,
+        },
+        template: `
+          <div>
+            <div
+              v-tooltip
+              class="js-look-for-tooltip"
+              title="foo">
+            </div>
+            <div
+              v-tooltip
+              title="bar">
+            </div>
+          </div>
+        `,
+      });
+
+      vm = new SomeComponent().$mount();
+    });
+
+    it('should have tooltip plugin applied to all instances', () => {
+      expect($(vm.$el).find('.js-look-for-tooltip').data('bs.tooltip')).toBeDefined();
+    });
+  });
+});
diff --git a/spec/lib/banzai/cross_project_reference_spec.rb b/spec/lib/banzai/cross_project_reference_spec.rb
index deaabceef1cb670d74a68fed1024bed4d3ed7196..787212581e2e6146e72d13f11a31572e9554a6f7 100644
--- a/spec/lib/banzai/cross_project_reference_spec.rb
+++ b/spec/lib/banzai/cross_project_reference_spec.rb
@@ -24,8 +24,8 @@ describe Banzai::CrossProjectReference, lib: true do
       it 'returns the referenced project' do
         project2 = double('referenced project')
 
-        expect(Project).to receive(:find_by_full_path).
-          with('cross/reference').and_return(project2)
+        expect(Project).to receive(:find_by_full_path)
+          .with('cross/reference').and_return(project2)
 
         expect(project_from_ref('cross/reference')).to eq project2
       end
diff --git a/spec/lib/banzai/filter/abstract_reference_filter_spec.rb b/spec/lib/banzai/filter/abstract_reference_filter_spec.rb
index 787c2372c5b9f908ea2c6d822e65d65a1be52475..27532f96f5635bf411928592a839bf1a072922f5 100644
--- a/spec/lib/banzai/filter/abstract_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/abstract_reference_filter_spec.rb
@@ -23,11 +23,11 @@ describe Banzai::Filter::AbstractReferenceFilter do
       doc = Nokogiri::HTML.fragment('')
       filter = described_class.new(doc, project: project)
 
-      expect(filter).to receive(:references_per_project).
-        and_return({ project.path_with_namespace => Set.new(%w[1]) })
+      expect(filter).to receive(:references_per_project)
+        .and_return({ project.path_with_namespace => Set.new(%w[1]) })
 
-      expect(filter.projects_per_reference).
-        to eq({ project.path_with_namespace => project })
+      expect(filter.projects_per_reference)
+        .to eq({ project.path_with_namespace => project })
     end
   end
 
@@ -37,26 +37,26 @@ describe Banzai::Filter::AbstractReferenceFilter do
 
     context 'with RequestStore disabled' do
       it 'returns a list of Projects for a list of paths' do
-        expect(filter.find_projects_for_paths([project.path_with_namespace])).
-          to eq([project])
+        expect(filter.find_projects_for_paths([project.path_with_namespace]))
+          .to eq([project])
       end
 
       it "return an empty array for paths that don't exist" do
-        expect(filter.find_projects_for_paths(['nonexistent/project'])).
-          to eq([])
+        expect(filter.find_projects_for_paths(['nonexistent/project']))
+          .to eq([])
       end
     end
 
     context 'with RequestStore enabled', :request_store do
       it 'returns a list of Projects for a list of paths' do
-        expect(filter.find_projects_for_paths([project.path_with_namespace])).
-          to eq([project])
+        expect(filter.find_projects_for_paths([project.path_with_namespace]))
+          .to eq([project])
       end
 
       context "when no project with that path exists" do
         it "returns no value" do
-          expect(filter.find_projects_for_paths(['nonexistent/project'])).
-            to eq([])
+          expect(filter.find_projects_for_paths(['nonexistent/project']))
+            .to eq([])
         end
 
         it "adds the ref to the project refs cache" do
@@ -75,8 +75,8 @@ describe Banzai::Filter::AbstractReferenceFilter do
           end
 
           it "return an empty array for paths that don't exist" do
-            expect(filter.find_projects_for_paths(['nonexistent/project'])).
-              to eq([])
+            expect(filter.find_projects_for_paths(['nonexistent/project']))
+              .to eq([])
           end
         end
       end
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 deadc36524cc72ba76069231d7c8f00bd195c290..60c27bc0d3c3f2e04b52c1fd05f93939fb997a49 100644
--- a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
@@ -28,15 +28,15 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
     it 'links to a valid two-dot reference' do
       doc = reference_filter("See #{reference2}")
 
-      expect(doc.css('a').first.attr('href')).
-        to eq urls.namespace_project_compare_url(project.namespace, project, range2.to_param)
+      expect(doc.css('a').first.attr('href'))
+        .to eq urls.project_compare_url(project, range2.to_param)
     end
 
     it 'links to a valid three-dot reference' do
       doc = reference_filter("See #{reference}")
 
-      expect(doc.css('a').first.attr('href')).
-        to eq urls.namespace_project_compare_url(project.namespace, project, range.to_param)
+      expect(doc.css('a').first.attr('href'))
+        .to eq urls.project_compare_url(project, range.to_param)
     end
 
     it 'links to a valid short ID' do
@@ -94,7 +94,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
       link = doc.css('a').first.attr('href')
 
       expect(link).not_to match %r(https?://)
-      expect(link).to eq urls.namespace_project_compare_url(project.namespace, project, from: commit1.id, to: commit2.id, only_path: true)
+      expect(link).to eq urls.project_compare_url(project, from: commit1.id, to: commit2.id, only_path: true)
     end
   end
 
@@ -105,15 +105,15 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
     it 'links to a valid reference' do
       doc = reference_filter("See #{reference}")
 
-      expect(doc.css('a').first.attr('href')).
-        to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param)
+      expect(doc.css('a').first.attr('href'))
+        .to eq urls.project_compare_url(project2, range.to_param)
     end
 
     it 'link has valid text' do
       doc = reference_filter("Fixed (#{reference}.)")
 
-      expect(doc.css('a').first.text).
-        to eql("#{project2.path_with_namespace}@#{commit1.short_id}...#{commit2.short_id}")
+      expect(doc.css('a').first.text)
+        .to eql("#{project2.path_with_namespace}@#{commit1.short_id}...#{commit2.short_id}")
     end
 
     it 'has valid text' do
@@ -140,15 +140,15 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
     it 'links to a valid reference' do
       doc = reference_filter("See #{reference}")
 
-      expect(doc.css('a').first.attr('href')).
-        to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param)
+      expect(doc.css('a').first.attr('href'))
+        .to eq urls.project_compare_url(project2, range.to_param)
     end
 
     it 'link has valid text' do
       doc = reference_filter("Fixed (#{reference}.)")
 
-      expect(doc.css('a').first.text).
-        to eql("#{project2.path}@#{commit1.short_id}...#{commit2.short_id}")
+      expect(doc.css('a').first.text)
+        .to eql("#{project2.path}@#{commit1.short_id}...#{commit2.short_id}")
     end
 
     it 'has valid text' do
@@ -175,15 +175,15 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
     it 'links to a valid reference' do
       doc = reference_filter("See #{reference}")
 
-      expect(doc.css('a').first.attr('href')).
-        to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param)
+      expect(doc.css('a').first.attr('href'))
+        .to eq urls.project_compare_url(project2, range.to_param)
     end
 
     it 'link has valid text' do
       doc = reference_filter("Fixed (#{reference}.)")
 
-      expect(doc.css('a').first.text).
-        to eql("#{project2.path}@#{commit1.short_id}...#{commit2.short_id}")
+      expect(doc.css('a').first.text)
+        .to eql("#{project2.path}@#{commit1.short_id}...#{commit2.short_id}")
     end
 
     it 'has valid text' do
@@ -205,7 +205,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
     let(:namespace) { create(: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') }
+    let(:reference) { urls.project_compare_url(project2, from: commit1.id, to: 'master') }
 
     before do
       range.project = project2
@@ -214,8 +214,8 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
     it 'links to a valid reference' do
       doc = reference_filter("See #{reference}")
 
-      expect(doc.css('a').first.attr('href')).
-        to eq reference
+      expect(doc.css('a').first.attr('href'))
+        .to eq reference
     end
 
     it 'links with adjacent text' do
diff --git a/spec/lib/banzai/filter/commit_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
index a19aac61229cd2d08e4027e65a96a64a8c425747..f6893641481df20e14390905d182f1fce22a20cb 100644
--- a/spec/lib/banzai/filter/commit_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
@@ -26,8 +26,8 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
         doc = reference_filter("See #{reference[0...size]}")
 
         expect(doc.css('a').first.text).to eq commit.short_id
-        expect(doc.css('a').first.attr('href')).
-          to eq urls.namespace_project_commit_url(project.namespace, project, reference)
+        expect(doc.css('a').first.attr('href'))
+          .to eq urls.project_commit_url(project, reference)
       end
     end
 
@@ -90,7 +90,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
       link = doc.css('a').first.attr('href')
 
       expect(link).not_to match %r(https?://)
-      expect(link).to eq urls.namespace_project_commit_url(project.namespace, project, reference, only_path: true)
+      expect(link).to eq urls.project_commit_url(project, reference, only_path: true)
     end
   end
 
@@ -175,13 +175,13 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
     let(:namespace) { create(: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) }
+    let(:reference) { urls.project_commit_url(project2, commit.id) }
 
     it 'links to a valid reference' do
       doc = reference_filter("See #{reference}")
 
-      expect(doc.css('a').first.attr('href')).
-        to eq urls.namespace_project_commit_url(project2.namespace, project2, commit.id)
+      expect(doc.css('a').first.attr('href'))
+        .to eq urls.project_commit_url(project2, commit.id)
     end
 
     it 'links with adjacent text' do
diff --git a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
index 76cefe112fb163173d848544bde53d13aa1a8a82..b7d82c36ddd5913c1be93c2369a4e6a64c3110f6 100644
--- a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
@@ -58,8 +58,8 @@ describe Banzai::Filter::ExternalIssueReferenceFilter, lib: true do
     end
 
     it 'escapes the title attribute' do
-      allow(project.external_issue_tracker).to receive(:title).
-        and_return(%{"></a>whatever<a title="})
+      allow(project.external_issue_tracker).to receive(:title)
+        .and_return(%{"></a>whatever<a title="})
 
       doc = filter("Issue #{reference}")
       expect(doc.text).to eq "Issue #{reference}"
@@ -88,12 +88,12 @@ describe Banzai::Filter::ExternalIssueReferenceFilter, lib: true do
 
       it 'queries the collection on the first call' do
         expect_any_instance_of(Project).to receive(:default_issues_tracker?).once.and_call_original
-        expect_any_instance_of(Project).to receive(:issue_reference_pattern).once.and_call_original
+        expect_any_instance_of(Project).to receive(:external_issue_reference_pattern).once.and_call_original
 
         not_cached = reference_filter.call("look for #{reference}", { project: project })
 
         expect_any_instance_of(Project).not_to receive(:default_issues_tracker?)
-        expect_any_instance_of(Project).not_to receive(:issue_reference_pattern)
+        expect_any_instance_of(Project).not_to receive(:external_issue_reference_pattern)
 
         cached = reference_filter.call("look for #{reference}", { project: project })
 
diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
index f1082495fcc4c921d982dae86965f82baa8d3738..a79d365d6c5ebfd517fd9f4b8b01199d65c0b970 100644
--- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
@@ -39,18 +39,11 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
 
     let(:reference) { "##{issue.iid}" }
 
-    it 'ignores valid references when using non-default tracker' do
-      allow(project).to receive(:default_issues_tracker?).and_return(false)
-
-      exp = act = "Issue #{reference}"
-      expect(reference_filter(act).to_html).to eq exp
-    end
-
     it 'links to a valid reference' do
       doc = reference_filter("Fixed #{reference}")
 
-      expect(doc.css('a').first.attr('href')).
-        to eq helper.url_for_issue(issue.iid, project)
+      expect(doc.css('a').first.attr('href'))
+        .to eq helper.url_for_issue(issue.iid, project)
     end
 
     it 'links with adjacent text' do
@@ -137,9 +130,9 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
     let(:reference) { "#{project2.path_with_namespace}##{issue.iid}" }
 
     it 'ignores valid references when cross-reference project uses external tracker' do
-      expect_any_instance_of(described_class).to receive(:find_object).
-        with(project2, issue.iid).
-        and_return(nil)
+      expect_any_instance_of(described_class).to receive(:find_object)
+        .with(project2, issue.iid)
+        .and_return(nil)
 
       exp = act = "Issue #{reference}"
       expect(reference_filter(act).to_html).to eq exp
@@ -148,8 +141,8 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
     it 'links to a valid reference' do
       doc = reference_filter("See #{reference}")
 
-      expect(doc.css('a').first.attr('href')).
-        to eq helper.url_for_issue(issue.iid, project2)
+      expect(doc.css('a').first.attr('href'))
+        .to eq helper.url_for_issue(issue.iid, project2)
     end
 
     it 'link has valid text' do
@@ -181,9 +174,9 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
     let(:reference) { "#{project2.path_with_namespace}##{issue.iid}" }
 
     it 'ignores valid references when cross-reference project uses external tracker' do
-      expect_any_instance_of(described_class).to receive(:find_object).
-        with(project2, issue.iid).
-        and_return(nil)
+      expect_any_instance_of(described_class).to receive(:find_object)
+        .with(project2, issue.iid)
+        .and_return(nil)
 
       exp = act = "Issue #{reference}"
       expect(reference_filter(act).to_html).to eq exp
@@ -192,8 +185,8 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
     it 'links to a valid reference' do
       doc = reference_filter("See #{reference}")
 
-      expect(doc.css('a').first.attr('href')).
-        to eq helper.url_for_issue(issue.iid, project2)
+      expect(doc.css('a').first.attr('href'))
+        .to eq helper.url_for_issue(issue.iid, project2)
     end
 
     it 'link has valid text' do
@@ -225,9 +218,9 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
     let(:reference) { "#{project2.path}##{issue.iid}" }
 
     it 'ignores valid references when cross-reference project uses external tracker' do
-      expect_any_instance_of(described_class).to receive(:find_object).
-        with(project2, issue.iid).
-        and_return(nil)
+      expect_any_instance_of(described_class).to receive(:find_object)
+        .with(project2, issue.iid)
+        .and_return(nil)
 
       exp = act = "Issue #{reference}"
       expect(reference_filter(act).to_html).to eq exp
@@ -236,8 +229,8 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
     it 'links to a valid reference' do
       doc = reference_filter("See #{reference}")
 
-      expect(doc.css('a').first.attr('href')).
-        to eq helper.url_for_issue(issue.iid, project2)
+      expect(doc.css('a').first.attr('href'))
+        .to eq helper.url_for_issue(issue.iid, project2)
     end
 
     it 'link has valid text' do
@@ -270,8 +263,8 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
     it 'links to a valid reference' do
       doc = reference_filter("See #{reference}")
 
-      expect(doc.css('a').first.attr('href')).
-        to eq reference
+      expect(doc.css('a').first.attr('href'))
+        .to eq reference
     end
 
     it 'links with adjacent text' do
@@ -292,8 +285,8 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
     it 'links to a valid reference' do
       doc = reference_filter("See #{reference_link}")
 
-      expect(doc.css('a').first.attr('href')).
-        to eq helper.url_for_issue(issue.iid, project2)
+      expect(doc.css('a').first.attr('href'))
+        .to eq helper.url_for_issue(issue.iid, project2)
     end
 
     it 'links with adjacent text' do
@@ -314,8 +307,8 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
     it 'links to a valid reference' do
       doc = reference_filter("See #{reference_link}")
 
-      expect(doc.css('a').first.attr('href')).
-        to eq helper.url_for_issue(issue.iid, project2) + "#note_123"
+      expect(doc.css('a').first.attr('href'))
+        .to eq helper.url_for_issue(issue.iid, project2) + "#note_123"
     end
 
     it 'links with adjacent text' do
@@ -330,32 +323,14 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
         doc = Nokogiri::HTML.fragment('')
         filter = described_class.new(doc, project: project)
 
-        expect(filter).to receive(:projects_per_reference).
-          and_return({ project.path_with_namespace => project })
-
-        expect(filter).to receive(:references_per_project).
-          and_return({ project.path_with_namespace => Set.new([issue.iid]) })
-
-        expect(filter.issues_per_project).
-          to eq({ project => { issue.iid => issue } })
-      end
-    end
-
-    context 'using an external issue tracker' do
-      it 'returns a Hash containing the issues per project' do
-        doc = Nokogiri::HTML.fragment('')
-        filter = described_class.new(doc, project: project)
-
-        expect(project).to receive(:default_issues_tracker?).and_return(false)
-
-        expect(filter).to receive(:projects_per_reference).
-          and_return({ project.path_with_namespace => project })
+        expect(filter).to receive(:projects_per_reference)
+          .and_return({ project.path_with_namespace => project })
 
-        expect(filter).to receive(:references_per_project).
-          and_return({ project.path_with_namespace => Set.new([1]) })
+        expect(filter).to receive(:references_per_project)
+          .and_return({ project.path_with_namespace => Set.new([issue.iid]) })
 
-        expect(filter.issues_per_project[project][1]).
-          to be_an_instance_of(ExternalIssue)
+        expect(filter.issues_per_project)
+          .to eq({ project => { issue.iid => issue } })
       end
     end
   end
diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb
index 284641fb20a9216e7e308b28dc38e24a38981087..8daef3ca6916553f9f75359fdc8c28f9e7147775 100644
--- a/spec/lib/banzai/filter/label_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb
@@ -45,7 +45,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
     link = doc.css('a').first.attr('href')
 
     expect(link).not_to match %r(https?://)
-    expect(link).to eq urls.namespace_project_issues_path(project.namespace, project, label_name: label.name)
+    expect(link).to eq urls.project_issues_path(project, label_name: label.name)
   end
 
   context 'project that does not exist referenced' do
@@ -72,8 +72,8 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
     it 'links to a valid reference' do
       doc = reference_filter("See #{reference}")
 
-      expect(doc.css('a').first.attr('href')).to eq urls.
-        namespace_project_issues_url(project.namespace, project, label_name: label.name)
+      expect(doc.css('a').first.attr('href')).to eq urls
+        .project_issues_url(project, label_name: label.name)
     end
 
     it 'links with adjacent text' do
@@ -95,8 +95,8 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
     it 'links to a valid reference' do
       doc = reference_filter("See #{reference}")
 
-      expect(doc.css('a').first.attr('href')).to eq urls.
-        namespace_project_issues_url(project.namespace, project, label_name: label.name)
+      expect(doc.css('a').first.attr('href')).to eq urls
+        .project_issues_url(project, label_name: label.name)
       expect(doc.text).to eq 'See gfm'
     end
 
@@ -119,8 +119,8 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
     it 'links to a valid reference' do
       doc = reference_filter("See #{reference}")
 
-      expect(doc.css('a').first.attr('href')).to eq urls.
-        namespace_project_issues_url(project.namespace, project, label_name: label.name)
+      expect(doc.css('a').first.attr('href')).to eq urls
+        .project_issues_url(project, label_name: label.name)
       expect(doc.text).to eq 'See 2fa'
     end
 
@@ -143,8 +143,8 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
     it 'links to a valid reference' do
       doc = reference_filter("See #{reference}")
 
-      expect(doc.css('a').first.attr('href')).to eq urls.
-        namespace_project_issues_url(project.namespace, project, label_name: label.name)
+      expect(doc.css('a').first.attr('href')).to eq urls
+        .project_issues_url(project, label_name: label.name)
       expect(doc.text).to eq 'See ?g.fm&'
     end
 
@@ -168,8 +168,8 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
     it 'links to a valid reference' do
       doc = reference_filter("See #{reference}")
 
-      expect(doc.css('a').first.attr('href')).to eq urls.
-        namespace_project_issues_url(project.namespace, project, label_name: label.name)
+      expect(doc.css('a').first.attr('href')).to eq urls
+        .project_issues_url(project, label_name: label.name)
       expect(doc.text).to eq 'See gfm references'
     end
 
@@ -192,8 +192,8 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
     it 'links to a valid reference' do
       doc = reference_filter("See #{reference}")
 
-      expect(doc.css('a').first.attr('href')).to eq urls.
-        namespace_project_issues_url(project.namespace, project, label_name: label.name)
+      expect(doc.css('a').first.attr('href')).to eq urls
+        .project_issues_url(project, label_name: label.name)
       expect(doc.text).to eq 'See 2 factor authentication'
     end
 
@@ -216,8 +216,8 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
     it 'links to a valid reference' do
       doc = reference_filter("See #{reference}")
 
-      expect(doc.css('a').first.attr('href')).to eq urls.
-        namespace_project_issues_url(project.namespace, project, label_name: label.name)
+      expect(doc.css('a').first.attr('href')).to eq urls
+        .project_issues_url(project, label_name: label.name)
       expect(doc.text).to eq 'See g.fm & references?'
     end
 
@@ -250,9 +250,9 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
         doc = reference_filter("See #{references}")
 
         expect(doc.css('a').map { |a| a.attr('href') }).to match_array([
-          urls.namespace_project_issues_url(project.namespace, project, label_name: bug.name),
-          urls.namespace_project_issues_url(project.namespace, project, label_name: feature_proposal.name),
-          urls.namespace_project_issues_url(project.namespace, project, label_name: technical_debt.name)
+          urls.project_issues_url(project, label_name: bug.name),
+          urls.project_issues_url(project, label_name: feature_proposal.name),
+          urls.project_issues_url(project, label_name: technical_debt.name)
         ])
         expect(doc.text).to eq 'See bug, feature proposal, technical debt'
       end
@@ -265,9 +265,9 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
         doc = reference_filter("See #{references}")
 
         expect(doc.css('a').map { |a| a.attr('href') }).to match_array([
-          urls.namespace_project_issues_url(project.namespace, project, label_name: bug.name),
-          urls.namespace_project_issues_url(project.namespace, project, label_name: feature_proposal.name),
-          urls.namespace_project_issues_url(project.namespace, project, label_name: technical_debt.name)
+          urls.project_issues_url(project, label_name: bug.name),
+          urls.project_issues_url(project, label_name: feature_proposal.name),
+          urls.project_issues_url(project, label_name: technical_debt.name)
         ])
         expect(doc.text).to eq 'See bug feature proposal technical debt'
       end
@@ -287,8 +287,8 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
     it 'links to a valid reference' do
       doc = reference_filter("See #{reference}")
 
-      expect(doc.css('a').first.attr('href')).to eq urls.
-        namespace_project_issues_url(project.namespace, project, label_name: label.name)
+      expect(doc.css('a').first.attr('href')).to eq urls
+        .project_issues_url(project, label_name: label.name)
     end
 
     it 'links with adjacent text' do
@@ -324,8 +324,8 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
       it 'links to a valid reference' do
         doc = reference_filter("See #{reference}", project: project)
 
-        expect(doc.css('a').first.attr('href')).to eq urls.
-          namespace_project_issues_url(project.namespace, project, label_name: group_label.name)
+        expect(doc.css('a').first.attr('href')).to eq urls
+          .project_issues_url(project, label_name: group_label.name)
         expect(doc.text).to eq 'See gfm references'
       end
 
@@ -347,8 +347,8 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
       it 'links to a valid reference' do
         doc = reference_filter("See #{reference}", project: project)
 
-        expect(doc.css('a').first.attr('href')).to eq urls.
-          namespace_project_issues_url(project.namespace, project, label_name: group_label.name)
+        expect(doc.css('a').first.attr('href')).to eq urls
+          .project_issues_url(project, label_name: group_label.name)
         expect(doc.text).to eq "See gfm references"
       end
 
@@ -373,9 +373,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
 
     it 'links to a valid reference' do
       expect(result.css('a').first.attr('href'))
-        .to eq urls.namespace_project_issues_url(project2.namespace,
-                                                 project2,
-                                                 label_name: label.name)
+        .to eq urls.project_issues_url(project2, label_name: label.name)
     end
 
     it 'has valid color' do
@@ -407,9 +405,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
 
     it 'links to a valid reference' do
       expect(result.css('a').first.attr('href'))
-        .to eq urls.namespace_project_issues_url(project2.namespace,
-                                                 project2,
-                                                 label_name: label.name)
+        .to eq urls.project_issues_url(project2, label_name: label.name)
     end
 
     it 'has valid color' do
@@ -441,14 +437,12 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
 
     it 'links to a valid reference' do
       expect(result.css('a').first.attr('href'))
-        .to eq urls.namespace_project_issues_url(project2.namespace,
-                                                 project2,
-                                                 label_name: label.name)
+        .to eq urls.project_issues_url(project2, label_name: label.name)
     end
 
     it 'has valid color' do
-      expect(result.css('a span').first.attr('style')).
-        to match /background-color: #00ff00/
+      expect(result.css('a span').first.attr('style'))
+        .to match /background-color: #00ff00/
     end
 
     it 'has valid link text' do
@@ -477,24 +471,22 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
 
     it 'points to referenced project issues page' do
       expect(result.css('a').first.attr('href'))
-        .to eq urls.namespace_project_issues_url(another_project.namespace,
-                                                 another_project,
-                                                 label_name: group_label.name)
+        .to eq urls.project_issues_url(another_project, label_name: group_label.name)
     end
 
     it 'has valid color' do
-      expect(result.css('a span').first.attr('style')).
-        to match /background-color: #00ff00/
+      expect(result.css('a span').first.attr('style'))
+        .to match /background-color: #00ff00/
     end
 
     it 'has valid link text' do
-      expect(result.css('a').first.text).
-        to eq "#{group_label.name} in #{another_project.name_with_namespace}"
+      expect(result.css('a').first.text)
+        .to eq "#{group_label.name} in #{another_project.name_with_namespace}"
     end
 
     it 'has valid text' do
-      expect(result.text).
-        to eq "See #{group_label.name} in #{another_project.name_with_namespace}"
+      expect(result.text)
+        .to eq "See #{group_label.name} in #{another_project.name_with_namespace}"
     end
 
     it 'ignores invalid IDs on the referenced label' do
@@ -513,25 +505,23 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
     let!(:result)          { reference_filter("See #{reference}", project: project) }
 
     it 'points to referenced project issues page' do
-      expect(result.css('a').first.attr('href')).
-        to eq urls.namespace_project_issues_url(another_project.namespace,
-                                                 another_project,
-                                                 label_name: group_label.name)
+      expect(result.css('a').first.attr('href'))
+        .to eq urls.project_issues_url(another_project, label_name: group_label.name)
     end
 
     it 'has valid color' do
-      expect(result.css('a span').first.attr('style')).
-        to match /background-color: #00ff00/
+      expect(result.css('a span').first.attr('style'))
+        .to match /background-color: #00ff00/
     end
 
     it 'has valid link text' do
-      expect(result.css('a').first.text).
-        to eq "#{group_label.name} in #{another_project.name}"
+      expect(result.css('a').first.text)
+        .to eq "#{group_label.name} in #{another_project.name}"
     end
 
     it 'has valid text' do
-      expect(result.text).
-        to eq "See #{group_label.name} in #{another_project.name}"
+      expect(result.text)
+        .to eq "See #{group_label.name} in #{another_project.name}"
     end
 
     it 'ignores invalid IDs on the referenced label' do
@@ -550,9 +540,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
 
     it 'points to referenced project issues page' do
       expect(result.css('a').first.attr('href'))
-        .to eq urls.namespace_project_issues_url(project.namespace,
-                                                 project,
-                                                 label_name: group_label.name)
+        .to eq urls.project_issues_url(project, label_name: group_label.name)
     end
 
     it 'has valid color' do
@@ -584,14 +572,12 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
 
     it 'points to referenced project issues page' do
       expect(result.css('a').first.attr('href'))
-        .to eq urls.namespace_project_issues_url(project.namespace,
-                                                 project,
-                                                 label_name: group_label.name)
+        .to eq urls.project_issues_url(project, label_name: group_label.name)
     end
 
     it 'has valid color' do
-      expect(result.css('a span').first.attr('style')).
-        to match /background-color: #00ff00/
+      expect(result.css('a span').first.attr('style'))
+        .to match /background-color: #00ff00/
     end
 
     it 'has valid link text' do
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 40232f6e4269eafcf298fcf7aca18ff0b5824f95..1ad329b64527ccb9231a5e95df03fa27f5d28cbb 100644
--- a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
@@ -36,8 +36,8 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do
     it 'links to a valid reference' do
       doc = reference_filter("See #{reference}")
 
-      expect(doc.css('a').first.attr('href')).to eq urls.
-        namespace_project_merge_request_url(project.namespace, project, merge)
+      expect(doc.css('a').first.attr('href')).to eq urls
+        .project_merge_request_url(project, merge)
     end
 
     it 'links with adjacent text' do
@@ -95,7 +95,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do
       link = doc.css('a').first.attr('href')
 
       expect(link).not_to match %r(https?://)
-      expect(link).to eq urls.namespace_project_merge_request_url(project.namespace, project, merge, only_path: true)
+      expect(link).to eq urls.project_merge_request_url(project, merge, only_path: true)
     end
   end
 
@@ -107,9 +107,8 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do
     it 'links to a valid reference' do
       doc = reference_filter("See #{reference}")
 
-      expect(doc.css('a').first.attr('href')).
-        to eq urls.namespace_project_merge_request_url(project2.namespace,
-                                                       project2, merge)
+      expect(doc.css('a').first.attr('href'))
+        .to eq urls.project_merge_request_url(project2, merge)
     end
 
     it 'link has valid text' do
@@ -141,9 +140,8 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do
     it 'links to a valid reference' do
       doc = reference_filter("See #{reference}")
 
-      expect(doc.css('a').first.attr('href')).
-        to eq urls.namespace_project_merge_request_url(project2.namespace,
-                                                      project2, merge)
+      expect(doc.css('a').first.attr('href'))
+        .to eq urls.project_merge_request_url(project2, merge)
     end
 
     it 'link has valid text' do
@@ -175,9 +173,8 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do
     it 'links to a valid reference' do
       doc = reference_filter("See #{reference}")
 
-      expect(doc.css('a').first.attr('href')).
-        to eq urls.namespace_project_merge_request_url(project2.namespace,
-                                                      project2, merge)
+      expect(doc.css('a').first.attr('href'))
+        .to eq urls.project_merge_request_url(project2, merge)
     end
 
     it 'link has valid text' do
@@ -203,13 +200,13 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do
     let(:namespace) { create(:namespace, name: 'cross-reference') }
     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' }
+    let(:reference) { urls.project_merge_request_url(project2, merge) + '/diffs#note_123' }
 
     it 'links to a valid reference' do
       doc = reference_filter("See #{reference}")
 
-      expect(doc.css('a').first.attr('href')).
-        to eq reference
+      expect(doc.css('a').first.attr('href'))
+        .to eq reference
     end
 
     it 'links with adjacent text' do
diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
index a317c751d3275ebc5034e61bc38c9c2b2161e3f8..7fab5613afcfcd40b49057471b5c7d9894fb7292 100644
--- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
@@ -44,16 +44,16 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
     link = doc.css('a').first.attr('href')
 
     expect(link).not_to match %r(https?://)
-    expect(link).to eq urls.
-      namespace_project_milestone_path(project.namespace, project, milestone)
+    expect(link).to eq urls
+      .project_milestone_path(project, milestone)
   end
 
   context 'Integer-based references' do
     it 'links to a valid reference' do
       doc = reference_filter("See #{reference}")
 
-      expect(doc.css('a').first.attr('href')).to eq urls.
-        namespace_project_milestone_url(project.namespace, project, milestone)
+      expect(doc.css('a').first.attr('href')).to eq urls
+        .project_milestone_url(project, milestone)
     end
 
     it 'links with adjacent text' do
@@ -75,8 +75,8 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
     it 'links to a valid reference' do
       doc = reference_filter("See #{reference}")
 
-      expect(doc.css('a').first.attr('href')).to eq urls.
-        namespace_project_milestone_url(project.namespace, project, milestone)
+      expect(doc.css('a').first.attr('href')).to eq urls
+        .project_milestone_url(project, milestone)
       expect(doc.text).to eq 'See gfm'
     end
 
@@ -99,8 +99,8 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
     it 'links to a valid reference' do
       doc = reference_filter("See #{reference}")
 
-      expect(doc.css('a').first.attr('href')).to eq urls.
-        namespace_project_milestone_url(project.namespace, project, milestone)
+      expect(doc.css('a').first.attr('href')).to eq urls
+        .project_milestone_url(project, milestone)
       expect(doc.text).to eq 'See gfm references'
     end
 
@@ -122,8 +122,8 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
     it 'links to a valid reference' do
       doc = reference_filter("See #{reference}")
 
-      expect(doc.css('a').first.attr('href')).to eq urls.
-        namespace_project_milestone_url(project.namespace, project, milestone)
+      expect(doc.css('a').first.attr('href')).to eq urls
+        .project_milestone_url(project, milestone)
     end
 
     it 'links with adjacent text' do
@@ -156,24 +156,22 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
     let!(:result)         { reference_filter("See #{reference}") }
 
     it 'points to referenced project milestone page' do
-      expect(result.css('a').first.attr('href')).to eq urls.
-        namespace_project_milestone_url(another_project.namespace,
-                                        another_project,
-                                        milestone)
+      expect(result.css('a').first.attr('href')).to eq urls
+        .project_milestone_url(another_project, milestone)
     end
 
     it 'link has valid text' do
       doc = reference_filter("See (#{reference}.)")
 
-      expect(doc.css('a').first.text).
-        to eq("#{milestone.name} in #{another_project.path_with_namespace}")
+      expect(doc.css('a').first.text)
+        .to eq("#{milestone.name} in #{another_project.path_with_namespace}")
     end
 
     it 'has valid text' do
       doc = reference_filter("See (#{reference}.)")
 
-      expect(doc.text).
-        to eq("See (#{milestone.name} in #{another_project.path_with_namespace}.)")
+      expect(doc.text)
+        .to eq("See (#{milestone.name} in #{another_project.path_with_namespace}.)")
     end
 
     it 'escapes the name attribute' do
@@ -181,8 +179,8 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
 
       doc = reference_filter("See #{reference}")
 
-      expect(doc.css('a').first.text).
-        to eq "#{milestone.name} in #{another_project.path_with_namespace}"
+      expect(doc.css('a').first.text)
+        .to eq "#{milestone.name} in #{another_project.path_with_namespace}"
     end
   end
 
@@ -195,24 +193,22 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
     let!(:result)         { reference_filter("See #{reference}") }
 
     it 'points to referenced project milestone page' do
-      expect(result.css('a').first.attr('href')).to eq urls.
-        namespace_project_milestone_url(another_project.namespace,
-                                        another_project,
-                                        milestone)
+      expect(result.css('a').first.attr('href')).to eq urls
+        .project_milestone_url(another_project, milestone)
     end
 
     it 'link has valid text' do
       doc = reference_filter("See (#{reference}.)")
 
-      expect(doc.css('a').first.text).
-        to eq("#{milestone.name} in #{another_project.path}")
+      expect(doc.css('a').first.text)
+        .to eq("#{milestone.name} in #{another_project.path}")
     end
 
     it 'has valid text' do
       doc = reference_filter("See (#{reference}.)")
 
-      expect(doc.text).
-        to eq("See (#{milestone.name} in #{another_project.path}.)")
+      expect(doc.text)
+        .to eq("See (#{milestone.name} in #{another_project.path}.)")
     end
 
     it 'escapes the name attribute' do
@@ -220,8 +216,8 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
 
       doc = reference_filter("See #{reference}")
 
-      expect(doc.css('a').first.text).
-        to eq "#{milestone.name} in #{another_project.path}"
+      expect(doc.css('a').first.text)
+        .to eq "#{milestone.name} in #{another_project.path}"
     end
   end
 
@@ -234,24 +230,22 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
     let!(:result)         { reference_filter("See #{reference}") }
 
     it 'points to referenced project milestone page' do
-      expect(result.css('a').first.attr('href')).to eq urls.
-        namespace_project_milestone_url(another_project.namespace,
-                                        another_project,
-                                        milestone)
+      expect(result.css('a').first.attr('href')).to eq urls
+        .project_milestone_url(another_project, milestone)
     end
 
     it 'link has valid text' do
       doc = reference_filter("See (#{reference}.)")
 
-      expect(doc.css('a').first.text).
-        to eq("#{milestone.name} in #{another_project.path}")
+      expect(doc.css('a').first.text)
+        .to eq("#{milestone.name} in #{another_project.path}")
     end
 
     it 'has valid text' do
       doc = reference_filter("See (#{reference}.)")
 
-      expect(doc.text).
-        to eq("See (#{milestone.name} in #{another_project.path}.)")
+      expect(doc.text)
+        .to eq("See (#{milestone.name} in #{another_project.path}.)")
     end
 
     it 'escapes the name attribute' do
@@ -259,8 +253,8 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
 
       doc = reference_filter("See #{reference}")
 
-      expect(doc.css('a').first.text).
-        to eq "#{milestone.name} in #{another_project.path}"
+      expect(doc.css('a').first.text)
+        .to eq "#{milestone.name} in #{another_project.path}"
     end
   end
 end
diff --git a/spec/lib/banzai/filter/redactor_filter_spec.rb b/spec/lib/banzai/filter/redactor_filter_spec.rb
index 97504aebed5d0b019af56464b597b0074375af40..b81cdbb89570bae5a4f4e41104d86942e747ab1a 100644
--- a/spec/lib/banzai/filter/redactor_filter_spec.rb
+++ b/spec/lib/banzai/filter/redactor_filter_spec.rb
@@ -33,9 +33,9 @@ describe Banzai::Filter::RedactorFilter, lib: true do
     end
 
     before do
-      allow(Banzai::ReferenceParser).to receive(:[]).
-        with('test').
-        and_return(parser_class)
+      allow(Banzai::ReferenceParser).to receive(:[])
+        .with('test')
+        .and_return(parser_class)
     end
 
     context 'valid projects' do
diff --git a/spec/lib/banzai/filter/reference_filter_spec.rb b/spec/lib/banzai/filter/reference_filter_spec.rb
index 55e681f6fafd96b482f2868ce31f34062c647258..ba0fa4a609aa4b8fd0d56c07e923def980280346 100644
--- a/spec/lib/banzai/filter/reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/reference_filter_spec.rb
@@ -8,8 +8,8 @@ describe Banzai::Filter::ReferenceFilter, lib: true do
       document = Nokogiri::HTML.fragment('<a href="foo">foo</a>')
       filter = described_class.new(document, project: project)
 
-      expect { |b| filter.each_node(&b) }.
-        to yield_with_args(an_instance_of(Nokogiri::XML::Element))
+      expect { |b| filter.each_node(&b) }
+        .to yield_with_args(an_instance_of(Nokogiri::XML::Element))
     end
 
     it 'returns an Enumerator when no block is given' do
diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb
index 1957ba739e267edc14b454f2f7ced2adf7b8b661..1ce7bd7706eba2dfdb21aafbceaa6c6451f0f91f 100644
--- a/spec/lib/banzai/filter/relative_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb
@@ -72,15 +72,15 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do
 
   it 'ignores ref if commit is passed' do
     doc = filter(link('non/existent.file'), commit: project.commit('empty-branch') )
-    expect(doc.at_css('a')['href']).
-      to eq "/#{project_path}/#{ref}/non/existent.file" # non-existent files have no leading blob/raw/tree
+    expect(doc.at_css('a')['href'])
+      .to eq "/#{project_path}/#{ref}/non/existent.file" # non-existent files have no leading blob/raw/tree
   end
 
   shared_examples :valid_repository do
     it 'rebuilds absolute URL for a file in the repo' do
       doc = filter(link('/doc/api/README.md'))
-      expect(doc.at_css('a')['href']).
-        to eq "/#{project_path}/blob/#{ref}/doc/api/README.md"
+      expect(doc.at_css('a')['href'])
+        .to eq "/#{project_path}/blob/#{ref}/doc/api/README.md"
     end
 
     it 'ignores absolute URLs with two leading slashes' do
@@ -90,71 +90,71 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do
 
     it 'rebuilds relative URL for a file in the repo' do
       doc = filter(link('doc/api/README.md'))
-      expect(doc.at_css('a')['href']).
-        to eq "/#{project_path}/blob/#{ref}/doc/api/README.md"
+      expect(doc.at_css('a')['href'])
+        .to eq "/#{project_path}/blob/#{ref}/doc/api/README.md"
     end
 
     it 'rebuilds relative URL for a file in the repo with leading ./' do
       doc = filter(link('./doc/api/README.md'))
-      expect(doc.at_css('a')['href']).
-        to eq "/#{project_path}/blob/#{ref}/doc/api/README.md"
+      expect(doc.at_css('a')['href'])
+        .to eq "/#{project_path}/blob/#{ref}/doc/api/README.md"
     end
 
     it 'rebuilds relative URL for a file in the repo up one directory' do
       relative_link = link('../api/README.md')
       doc = filter(relative_link, requested_path: 'doc/update/7.14-to-8.0.md')
 
-      expect(doc.at_css('a')['href']).
-        to eq "/#{project_path}/blob/#{ref}/doc/api/README.md"
+      expect(doc.at_css('a')['href'])
+        .to eq "/#{project_path}/blob/#{ref}/doc/api/README.md"
     end
 
     it 'rebuilds relative URL for a file in the repo up multiple directories' do
       relative_link = link('../../../api/README.md')
       doc = filter(relative_link, requested_path: 'doc/foo/bar/baz/README.md')
 
-      expect(doc.at_css('a')['href']).
-        to eq "/#{project_path}/blob/#{ref}/doc/api/README.md"
+      expect(doc.at_css('a')['href'])
+        .to eq "/#{project_path}/blob/#{ref}/doc/api/README.md"
     end
 
     it 'rebuilds relative URL for a file in the repository root' do
       relative_link = link('../README.md')
       doc = filter(relative_link, requested_path: 'doc/some-file.md')
 
-      expect(doc.at_css('a')['href']).
-        to eq "/#{project_path}/blob/#{ref}/README.md"
+      expect(doc.at_css('a')['href'])
+        .to eq "/#{project_path}/blob/#{ref}/README.md"
     end
 
     it 'rebuilds relative URL for a file in the repo with an anchor' do
       doc = filter(link('README.md#section'))
-      expect(doc.at_css('a')['href']).
-        to eq "/#{project_path}/blob/#{ref}/README.md#section"
+      expect(doc.at_css('a')['href'])
+        .to eq "/#{project_path}/blob/#{ref}/README.md#section"
     end
 
     it 'rebuilds relative URL for a directory in the repo' do
       doc = filter(link('doc/api/'))
-      expect(doc.at_css('a')['href']).
-        to eq "/#{project_path}/tree/#{ref}/doc/api"
+      expect(doc.at_css('a')['href'])
+        .to eq "/#{project_path}/tree/#{ref}/doc/api"
     end
 
     it 'rebuilds relative URL for an image in the repo' do
       doc = filter(image('files/images/logo-black.png'))
 
-      expect(doc.at_css('img')['src']).
-        to eq "/#{project_path}/raw/#{ref}/files/images/logo-black.png"
+      expect(doc.at_css('img')['src'])
+        .to eq "/#{project_path}/raw/#{ref}/files/images/logo-black.png"
     end
 
     it 'rebuilds relative URL for link to an image in the repo' do
       doc = filter(link('files/images/logo-black.png'))
 
-      expect(doc.at_css('a')['href']).
-        to eq "/#{project_path}/raw/#{ref}/files/images/logo-black.png"
+      expect(doc.at_css('a')['href'])
+        .to eq "/#{project_path}/raw/#{ref}/files/images/logo-black.png"
     end
 
     it 'rebuilds relative URL for a video in the repo' do
       doc = filter(video('files/videos/intro.mp4'), commit: project.commit('video'), ref: 'video')
 
-      expect(doc.at_css('video')['src']).
-        to eq "/#{project_path}/raw/video/files/videos/intro.mp4"
+      expect(doc.at_css('video')['src'])
+        .to eq "/#{project_path}/raw/video/files/videos/intro.mp4"
     end
 
     it 'does not modify relative URL with an anchor only' do
diff --git a/spec/lib/banzai/filter/sanitization_filter_spec.rb b/spec/lib/banzai/filter/sanitization_filter_spec.rb
index fb7862f49a26cc1dc00419c2e75a0733f5419d79..a8a0aa6d395c5972b392c9b1068dfe407a13302c 100644
--- a/spec/lib/banzai/filter/sanitization_filter_spec.rb
+++ b/spec/lib/banzai/filter/sanitization_filter_spec.rb
@@ -221,8 +221,8 @@ describe Banzai::Filter::SanitizationFilter, lib: true do
     end
 
     it 'disallows invalid URIs' do
-      expect(Addressable::URI).to receive(:parse).with('foo://example.com').
-        and_raise(Addressable::URI::InvalidURIError)
+      expect(Addressable::URI).to receive(:parse).with('foo://example.com')
+        .and_raise(Addressable::URI::InvalidURIError)
 
       input = '<a href="foo://example.com">Foo</a>'
       output = filter(input)
diff --git a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
index e036514d283e3647fd22c65745f63abfaf9b444d..9704db0c221944381e91c564085bd49837e20b13 100644
--- a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
@@ -22,8 +22,8 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do
     it 'links to a valid reference' do
       doc = reference_filter("See #{reference}")
 
-      expect(doc.css('a').first.attr('href')).to eq urls.
-        namespace_project_snippet_url(project.namespace, project, snippet)
+      expect(doc.css('a').first.attr('href')).to eq urls
+        .project_snippet_url(project, snippet)
     end
 
     it 'links with adjacent text' do
@@ -75,7 +75,7 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do
       link = doc.css('a').first.attr('href')
 
       expect(link).not_to match %r(https?://)
-      expect(link).to eq urls.namespace_project_snippet_url(project.namespace, project, snippet, only_path: true)
+      expect(link).to eq urls.project_snippet_url(project, snippet, only_path: true)
     end
   end
 
@@ -88,8 +88,8 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do
     it 'links to a valid reference' do
       doc = reference_filter("See #{reference}")
 
-      expect(doc.css('a').first.attr('href')).
-        to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
+      expect(doc.css('a').first.attr('href'))
+        .to eq urls.project_snippet_url(project2, snippet)
     end
 
     it 'link has valid text' do
@@ -121,8 +121,8 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do
     it 'links to a valid reference' do
       doc = reference_filter("See #{reference}")
 
-      expect(doc.css('a').first.attr('href')).
-        to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
+      expect(doc.css('a').first.attr('href'))
+        .to eq urls.project_snippet_url(project2, snippet)
     end
 
     it 'link has valid text' do
@@ -154,8 +154,8 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do
     it 'links to a valid reference' do
       doc = reference_filter("See #{reference}")
 
-      expect(doc.css('a').first.attr('href')).
-        to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
+      expect(doc.css('a').first.attr('href'))
+        .to eq urls.project_snippet_url(project2, snippet)
     end
 
     it 'link has valid text' do
@@ -181,13 +181,13 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do
     let(:namespace) { create(:namespace, name: 'cross-reference') }
     let(:project2)  { create(:empty_project, :public, namespace: namespace) }
     let(:snippet)   { create(:project_snippet, project: project2) }
-    let(:reference) { urls.namespace_project_snippet_url(project2.namespace, project2, snippet) }
+    let(:reference) { urls.project_snippet_url(project2, snippet) }
 
     it 'links to a valid reference' do
       doc = reference_filter("See #{reference}")
 
-      expect(doc.css('a').first.attr('href')).
-        to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
+      expect(doc.css('a').first.attr('href'))
+        .to eq urls.project_snippet_url(project2, snippet)
     end
 
     it 'links with adjacent text' do
diff --git a/spec/lib/banzai/filter/upload_link_filter_spec.rb b/spec/lib/banzai/filter/upload_link_filter_spec.rb
index 639cac6406aa9ede6440ddbc9077433d5910b8a7..6327ca8bbfdd17da729ad5b52993e7b708d1e937 100644
--- a/spec/lib/banzai/filter/upload_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/upload_link_filter_spec.rb
@@ -51,22 +51,22 @@ describe Banzai::Filter::UploadLinkFilter, lib: true do
   context 'with a valid repository' do
     it 'rebuilds relative URL for a link' do
       doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
-      expect(doc.at_css('a')['href']).
-        to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
+      expect(doc.at_css('a')['href'])
+        .to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
 
       doc = filter(nested_link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
-      expect(doc.at_css('a')['href']).
-        to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
+      expect(doc.at_css('a')['href'])
+        .to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
     end
 
     it 'rebuilds relative URL for an image' do
       doc = filter(image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
-      expect(doc.at_css('img')['src']).
-        to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
+      expect(doc.at_css('img')['src'])
+        .to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
 
       doc = filter(nested_image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
-      expect(doc.at_css('img')['src']).
-        to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
+      expect(doc.at_css('img')['src'])
+        .to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
     end
 
     it 'does not modify absolute URL' do
@@ -79,10 +79,10 @@ describe Banzai::Filter::UploadLinkFilter, lib: true do
       escaped = Addressable::URI.escape(path)
 
       # Stub these methods so the file doesn't actually need to be in the repo
-      allow_any_instance_of(described_class).
-        to receive(:file_exists?).and_return(true)
-      allow_any_instance_of(described_class).
-        to receive(:image?).with(path).and_return(true)
+      allow_any_instance_of(described_class)
+        .to receive(:file_exists?).and_return(true)
+      allow_any_instance_of(described_class)
+        .to receive(:image?).with(path).and_return(true)
 
       doc = filter(image(escaped))
       expect(doc.at_css('img')['src']).to match "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/%ED%95%9C%EA%B8%80.png"
diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb
index edf3846b7429d5c43e111d91bdc9784be50b6c0d..77561e00573c34078fb092203853413726cdcb2b 100644
--- a/spec/lib/banzai/filter/user_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb
@@ -43,7 +43,7 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do
 
       expect(doc.css('a').length).to eq 1
       expect(doc.css('a').first.attr('href'))
-        .to eq urls.namespace_project_url(project.namespace, project)
+        .to eq urls.project_url(project)
     end
 
     it 'includes a data-author attribute when there is an author' do
diff --git a/spec/lib/banzai/note_renderer_spec.rb b/spec/lib/banzai/note_renderer_spec.rb
index 49556074278ae67931989f84dc48d1a8248ae1e2..32764bee5eb178cd94cfbd113afc2cc4bc09a7e6 100644
--- a/spec/lib/banzai/note_renderer_spec.rb
+++ b/spec/lib/banzai/note_renderer_spec.rb
@@ -8,15 +8,15 @@ describe Banzai::NoteRenderer do
       wiki = double(:wiki)
       user = double(:user)
 
-      expect(Banzai::ObjectRenderer).to receive(:new).
-        with(project, user,
+      expect(Banzai::ObjectRenderer).to receive(:new)
+        .with(project, user,
              requested_path: 'foo',
              project_wiki: wiki,
-             ref: 'bar').
-        and_call_original
+             ref: 'bar')
+        .and_call_original
 
-      expect_any_instance_of(Banzai::ObjectRenderer).
-        to receive(:render).with([note], :note)
+      expect_any_instance_of(Banzai::ObjectRenderer)
+        .to receive(:render).with([note], :note)
 
       described_class.render([note], project, user, 'foo', wiki, 'bar')
     end
diff --git a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1eb90dc1847454757ffeccde950c28e2358b7220
--- /dev/null
+++ b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
@@ -0,0 +1,29 @@
+require 'rails_helper'
+
+describe Banzai::Pipeline::GfmPipeline do
+  describe 'integration between parsing regular and external issue references' do
+    let(:project) { create(:redmine_project, :public) }
+
+    it 'allows to use shorthand external reference syntax for Redmine' do
+      markdown = '#12'
+
+      result = described_class.call(markdown, project: project)[:output]
+      link = result.css('a').first
+
+      expect(link['href']).to eq 'http://redmine/projects/project_name_in_redmine/issues/12'
+    end
+
+    it 'parses cross-project references to regular issues' do
+      other_project = create(:empty_project, :public)
+      issue = create(:issue, project: other_project)
+      markdown = issue.to_reference(project, full: true)
+
+      result = described_class.call(markdown, project: project)[:output]
+      link = result.css('a').first
+
+      expect(link['href']).to eq(
+        Gitlab::Routing.url_helpers.project_issue_path(other_project, issue)
+      )
+    end
+  end
+end
diff --git a/spec/lib/banzai/redactor_spec.rb b/spec/lib/banzai/redactor_spec.rb
index e6f2963193cea70ed56c054519a8989b16ffcbb0..81ae5685b1011737a9876c687425e271e94c2db6 100644
--- a/spec/lib/banzai/redactor_spec.rb
+++ b/spec/lib/banzai/redactor_spec.rb
@@ -12,11 +12,11 @@ describe Banzai::Redactor do
       end
 
       it 'redacts an array of documents' do
-        doc1 = Nokogiri::HTML.
-               fragment('<a class="gfm" data-reference-type="issue">foo</a>')
+        doc1 = Nokogiri::HTML
+               .fragment('<a class="gfm" data-reference-type="issue">foo</a>')
 
-        doc2 = Nokogiri::HTML.
-               fragment('<a class="gfm" data-reference-type="issue">bar</a>')
+        doc2 = Nokogiri::HTML
+               .fragment('<a class="gfm" data-reference-type="issue">bar</a>')
 
         redacted_data = redactor.redact([doc1, doc2])
 
@@ -93,9 +93,9 @@ describe Banzai::Redactor do
       doc = Nokogiri::HTML.fragment('<a href="foo">foo</a>')
       node = doc.children[0]
 
-      expect(redactor).to receive(:nodes_visible_to_user).
-        with([node]).
-        and_return(Set.new)
+      expect(redactor).to receive(:nodes_visible_to_user)
+        .with([node])
+        .and_return(Set.new)
 
       redactor.redact_document_nodes([{ document: doc, nodes: [node] }])
 
@@ -108,10 +108,10 @@ describe Banzai::Redactor do
       doc = Nokogiri::HTML.fragment('<a data-reference-type="issue"></a>')
       node = doc.children[0]
 
-      expect_any_instance_of(Banzai::ReferenceParser::IssueParser).
-        to receive(:nodes_visible_to_user).
-        with(user, [node]).
-        and_return([node])
+      expect_any_instance_of(Banzai::ReferenceParser::IssueParser)
+        .to receive(:nodes_visible_to_user)
+        .with(user, [node])
+        .and_return([node])
 
       expect(redactor.nodes_visible_to_user([node])).to eq(Set.new([node]))
     end
diff --git a/spec/lib/banzai/reference_parser/base_parser_spec.rb b/spec/lib/banzai/reference_parser/base_parser_spec.rb
index 76fab93821aa5cfa194a982ffc02fe1e7c18b302..b444ca05b8ecae41c8af4789040d23eaabb2252e 100644
--- a/spec/lib/banzai/reference_parser/base_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/base_parser_spec.rb
@@ -54,8 +54,8 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
   describe '#referenced_by' do
     context 'when references_relation is implemented' do
       it 'returns a collection of objects' do
-        links = Nokogiri::HTML.fragment("<a data-foo='#{user.id}'></a>").
-          children
+        links = Nokogiri::HTML.fragment("<a data-foo='#{user.id}'></a>")
+          .children
 
         expect(subject).to receive(:references_relation).and_return(User)
         expect(subject.referenced_by(links)).to eq([user])
@@ -66,8 +66,8 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
       it 'raises NotImplementedError' do
         links = Nokogiri::HTML.fragment('<a data-foo="1"></a>').children
 
-        expect { subject.referenced_by(links) }.
-          to raise_error(NotImplementedError)
+        expect { subject.referenced_by(links) }
+          .to raise_error(NotImplementedError)
       end
     end
   end
@@ -80,8 +80,8 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
 
   describe '#gather_attributes_per_project' do
     it 'returns a Hash containing attribute values per project' do
-      link = Nokogiri::HTML.fragment('<a data-project="1" data-foo="2"></a>').
-        children[0]
+      link = Nokogiri::HTML.fragment('<a data-project="1" data-foo="2"></a>')
+        .children[0]
 
       hash = subject.gather_attributes_per_project([link], 'data-foo')
 
@@ -95,19 +95,19 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
     it 'returns a Hash grouping objects per node' do
       link = double(:link)
 
-      expect(link).to receive(:has_attribute?).
-        with('data-user').
-        and_return(true)
+      expect(link).to receive(:has_attribute?)
+        .with('data-user')
+        .and_return(true)
 
-      expect(link).to receive(:attr).
-        with('data-user').
-        and_return(user.id.to_s)
+      expect(link).to receive(:attr)
+        .with('data-user')
+        .and_return(user.id.to_s)
 
       nodes = [link]
 
-      expect(subject).to receive(:unique_attribute_values).
-        with(nodes, 'data-user').
-        and_return([user.id.to_s])
+      expect(subject).to receive(:unique_attribute_values)
+        .with(nodes, 'data-user')
+        .and_return([user.id.to_s])
 
       hash = subject.grouped_objects_for_nodes(nodes, User, 'data-user')
 
@@ -117,20 +117,20 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
     it 'returns an empty Hash when entry does not exist in the database', :request_store do
       link = double(:link)
 
-      expect(link).to receive(:has_attribute?).
-          with('data-user').
-          and_return(true)
+      expect(link).to receive(:has_attribute?)
+          .with('data-user')
+          .and_return(true)
 
-      expect(link).to receive(:attr).
-          with('data-user').
-          and_return('1')
+      expect(link).to receive(:attr)
+          .with('data-user')
+          .and_return('1')
 
       nodes = [link]
       bad_id = user.id + 100
 
-      expect(subject).to receive(:unique_attribute_values).
-          with(nodes, 'data-user').
-          and_return([bad_id.to_s])
+      expect(subject).to receive(:unique_attribute_values)
+          .with(nodes, 'data-user')
+          .and_return([bad_id.to_s])
 
       hash = subject.grouped_objects_for_nodes(nodes, User, 'data-user')
 
@@ -142,15 +142,15 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
     it 'returns an Array of unique values' do
       link = double(:link)
 
-      expect(link).to receive(:has_attribute?).
-        with('data-foo').
-        twice.
-        and_return(true)
+      expect(link).to receive(:has_attribute?)
+        .with('data-foo')
+        .twice
+        .and_return(true)
 
-      expect(link).to receive(:attr).
-        with('data-foo').
-        twice.
-        and_return('1')
+      expect(link).to receive(:attr)
+        .with('data-foo')
+        .twice
+        .and_return('1')
 
       nodes = [link, link]
 
@@ -167,9 +167,9 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
       instance = dummy.new(project, user)
       document = Nokogiri::HTML.fragment('<a class="gfm"></a><a class="gfm" data-reference-type="test"></a>')
 
-      expect(instance).to receive(:gather_references).
-        with([document.children[1]]).
-        and_return([user])
+      expect(instance).to receive(:gather_references)
+        .with([document.children[1]])
+        .and_return([user])
 
       expect(instance.process([document])).to eq([user])
     end
@@ -179,9 +179,9 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
     let(:link) { double(:link) }
 
     it 'does not process links a user can not reference' do
-      expect(subject).to receive(:nodes_user_can_reference).
-        with(user, [link]).
-        and_return([])
+      expect(subject).to receive(:nodes_user_can_reference)
+        .with(user, [link])
+        .and_return([])
 
       expect(subject).to receive(:referenced_by).with([])
 
@@ -189,13 +189,13 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
     end
 
     it 'does not process links a user can not see' do
-      expect(subject).to receive(:nodes_user_can_reference).
-        with(user, [link]).
-        and_return([link])
+      expect(subject).to receive(:nodes_user_can_reference)
+        .with(user, [link])
+        .and_return([link])
 
-      expect(subject).to receive(:nodes_visible_to_user).
-        with(user, [link]).
-        and_return([])
+      expect(subject).to receive(:nodes_visible_to_user)
+        .with(user, [link])
+        .and_return([])
 
       expect(subject).to receive(:referenced_by).with([])
 
@@ -203,13 +203,13 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
     end
 
     it 'returns the references if a user can reference and see a link' do
-      expect(subject).to receive(:nodes_user_can_reference).
-        with(user, [link]).
-        and_return([link])
+      expect(subject).to receive(:nodes_user_can_reference)
+        .with(user, [link])
+        .and_return([link])
 
-      expect(subject).to receive(:nodes_visible_to_user).
-        with(user, [link]).
-        and_return([link])
+      expect(subject).to receive(:nodes_visible_to_user)
+        .with(user, [link])
+        .and_return([link])
 
       expect(subject).to receive(:referenced_by).with([link])
 
@@ -221,8 +221,8 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
     it 'delegates the permissions check to the Ability class' do
       user = double(:user)
 
-      expect(Ability).to receive(:allowed?).
-        with(user, :read_project, project)
+      expect(Ability).to receive(:allowed?)
+        .with(user, :read_project, project)
 
       subject.can?(user, :read_project, project)
     end
@@ -230,8 +230,8 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
 
   describe '#find_projects_for_hash_keys' do
     it 'returns a list of Projects' do
-      expect(subject.find_projects_for_hash_keys(project.id => project)).
-        to eq([project])
+      expect(subject.find_projects_for_hash_keys(project.id => project))
+        .to eq([project])
     end
   end
 
@@ -243,8 +243,8 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
         expect(collection).to receive(:where).twice.and_call_original
 
         2.times do
-          expect(subject.collection_objects_for_ids(collection, [user.id])).
-            to eq([user])
+          expect(subject.collection_objects_for_ids(collection, [user.id]))
+            .to eq([user])
         end
       end
     end
@@ -258,8 +258,8 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
       end
 
       it 'queries the collection on the first call' do
-        expect(subject.collection_objects_for_ids(User, [user.id])).
-          to eq([user])
+        expect(subject.collection_objects_for_ids(User, [user.id]))
+          .to eq([user])
       end
 
       it 'does not query previously queried objects' do
@@ -268,34 +268,34 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
         expect(collection).to receive(:where).once.and_call_original
 
         2.times do
-          expect(subject.collection_objects_for_ids(collection, [user.id])).
-            to eq([user])
+          expect(subject.collection_objects_for_ids(collection, [user.id]))
+            .to eq([user])
         end
       end
 
       it 'casts String based IDs to Fixnums before querying objects' do
         2.times do
-          expect(subject.collection_objects_for_ids(User, [user.id.to_s])).
-            to eq([user])
+          expect(subject.collection_objects_for_ids(User, [user.id.to_s]))
+            .to eq([user])
         end
       end
 
       it 'queries any additional objects after the first call' do
         other_user = create(:user)
 
-        expect(subject.collection_objects_for_ids(User, [user.id])).
-          to eq([user])
+        expect(subject.collection_objects_for_ids(User, [user.id]))
+          .to eq([user])
 
-        expect(subject.collection_objects_for_ids(User, [user.id, other_user.id])).
-          to eq([user, other_user])
+        expect(subject.collection_objects_for_ids(User, [user.id, other_user.id]))
+          .to eq([user, other_user])
       end
 
       it 'caches objects on a per collection class basis' do
-        expect(subject.collection_objects_for_ids(User, [user.id])).
-          to eq([user])
+        expect(subject.collection_objects_for_ids(User, [user.id]))
+          .to eq([user])
 
-        expect(subject.collection_objects_for_ids(Project, [project.id])).
-          to eq([project])
+        expect(subject.collection_objects_for_ids(Project, [project.id]))
+          .to eq([project])
       end
     end
   end
diff --git a/spec/lib/banzai/reference_parser/commit_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_parser_spec.rb
index 583ce63a8ab728efce996e12aae570f355e32a21..a314a6119cbdb775d1d74eb72db5185d07076631 100644
--- a/spec/lib/banzai/reference_parser/commit_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/commit_parser_spec.rb
@@ -32,30 +32,30 @@ describe Banzai::ReferenceParser::CommitParser, lib: true do
         it 'returns an Array of commits' do
           commit = double(:commit)
 
-          allow_any_instance_of(Project).to receive(:valid_repo?).
-            and_return(true)
+          allow_any_instance_of(Project).to receive(:valid_repo?)
+            .and_return(true)
 
-          expect(subject).to receive(:find_commits).
-            with(project, ['123']).
-            and_return([commit])
+          expect(subject).to receive(:find_commits)
+            .with(project, ['123'])
+            .and_return([commit])
 
           expect(subject.referenced_by([link])).to eq([commit])
         end
 
         it 'returns an empty Array when the commit could not be found' do
-          allow_any_instance_of(Project).to receive(:valid_repo?).
-            and_return(true)
+          allow_any_instance_of(Project).to receive(:valid_repo?)
+            .and_return(true)
 
-          expect(subject).to receive(:find_commits).
-            with(project, ['123']).
-            and_return([])
+          expect(subject).to receive(:find_commits)
+            .with(project, ['123'])
+            .and_return([])
 
           expect(subject.referenced_by([link])).to eq([])
         end
 
         it 'skips projects without valid repositories' do
-          allow_any_instance_of(Project).to receive(:valid_repo?).
-            and_return(false)
+          allow_any_instance_of(Project).to receive(:valid_repo?)
+            .and_return(false)
 
           expect(subject.referenced_by([link])).to eq([])
         end
@@ -63,8 +63,8 @@ describe Banzai::ReferenceParser::CommitParser, lib: true do
 
       context 'when the link does not have a data-commit attribute' do
         it 'returns an empty Array' do
-          allow_any_instance_of(Project).to receive(:valid_repo?).
-            and_return(true)
+          allow_any_instance_of(Project).to receive(:valid_repo?)
+            .and_return(true)
 
           expect(subject.referenced_by([link])).to eq([])
         end
@@ -73,8 +73,8 @@ describe Banzai::ReferenceParser::CommitParser, lib: true do
 
     context 'when the link does not have a data-project attribute' do
       it 'returns an empty Array' do
-        allow_any_instance_of(Project).to receive(:valid_repo?).
-          and_return(true)
+        allow_any_instance_of(Project).to receive(:valid_repo?)
+          .and_return(true)
 
         expect(subject.referenced_by([link])).to eq([])
       end
diff --git a/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
index 8c0f5d7df97d6216ea1a386230dc413779744dc8..5dca5e784da760c80800d9dd38cb4f2856615e85 100644
--- a/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
@@ -32,17 +32,17 @@ describe Banzai::ReferenceParser::CommitRangeParser, lib: true do
         it 'returns an Array of commit ranges' do
           range = double(:range)
 
-          expect(subject).to receive(:find_object).
-            with(project, '123..456').
-            and_return(range)
+          expect(subject).to receive(:find_object)
+            .with(project, '123..456')
+            .and_return(range)
 
           expect(subject.referenced_by([link])).to eq([range])
         end
 
         it 'returns an empty Array when the commit range could not be found' do
-          expect(subject).to receive(:find_object).
-            with(project, '123..456').
-            and_return(nil)
+          expect(subject).to receive(:find_object)
+            .with(project, '123..456')
+            .and_return(nil)
 
           expect(subject.referenced_by([link])).to eq([])
         end
@@ -88,17 +88,17 @@ describe Banzai::ReferenceParser::CommitRangeParser, lib: true do
     it 'returns an Array of range objects' do
       range = double(:commit)
 
-      expect(subject).to receive(:find_object).
-        with(project, '123..456').
-        and_return(range)
+      expect(subject).to receive(:find_object)
+        .with(project, '123..456')
+        .and_return(range)
 
       expect(subject.find_ranges(project, ['123..456'])).to eq([range])
     end
 
     it 'skips ranges that could not be found' do
-      expect(subject).to receive(:find_object).
-        with(project, '123..456').
-        and_return(nil)
+      expect(subject).to receive(:find_object)
+        .with(project, '123..456')
+        .and_return(nil)
 
       expect(subject.find_ranges(project, ['123..456'])).to eq([])
     end
diff --git a/spec/lib/banzai/reference_parser/issue_parser_spec.rb b/spec/lib/banzai/reference_parser/issue_parser_spec.rb
index 7031c47231cc1792e74e493552112d0386c29706..acdd23f81f34bb1c9031699c846018751ddabe14 100644
--- a/spec/lib/banzai/reference_parser/issue_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/issue_parser_spec.rb
@@ -18,17 +18,17 @@ describe Banzai::ReferenceParser::IssueParser, lib: true do
       it_behaves_like "referenced feature visibility", "issues"
 
       it 'returns the nodes when the user can read the issue' do
-        expect(Ability).to receive(:issues_readable_by_user).
-          with([issue], user).
-          and_return([issue])
+        expect(Ability).to receive(:issues_readable_by_user)
+          .with([issue], user)
+          .and_return([issue])
 
         expect(subject.nodes_visible_to_user(user, [link])).to eq([link])
       end
 
       it 'returns an empty Array when the user can not read the issue' do
-        expect(Ability).to receive(:issues_readable_by_user).
-          with([issue], user).
-          and_return([])
+        expect(Ability).to receive(:issues_readable_by_user)
+          .with([issue], user)
+          .and_return([])
 
         expect(subject.nodes_visible_to_user(user, [link])).to eq([])
       end
@@ -39,16 +39,6 @@ describe Banzai::ReferenceParser::IssueParser, lib: true do
         expect(subject.nodes_visible_to_user(user, [link])).to eq([])
       end
     end
-
-    context 'when the project uses an external issue tracker' do
-      it 'returns all nodes' do
-        link = double(:link)
-
-        expect(project).to receive(:external_issue_tracker).and_return(true)
-
-        expect(subject.nodes_visible_to_user(user, [link])).to eq([link])
-      end
-    end
   end
 
   describe '#referenced_by' do
diff --git a/spec/lib/banzai/reference_parser/user_parser_spec.rb b/spec/lib/banzai/reference_parser/user_parser_spec.rb
index 4d560667342b96f933e185fb3a7ef761f780d34f..dfebb971f3a4fe7094b56da2c1ed7dff7808d665 100644
--- a/spec/lib/banzai/reference_parser/user_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/user_parser_spec.rb
@@ -96,17 +96,17 @@ describe Banzai::ReferenceParser::UserParser, lib: true do
         end
 
         it 'returns the nodes if the user can read the group' do
-          expect(Ability).to receive(:allowed?).
-            with(user, :read_group, group).
-            and_return(true)
+          expect(Ability).to receive(:allowed?)
+            .with(user, :read_group, group)
+            .and_return(true)
 
           expect(subject.nodes_visible_to_user(user, [link])).to eq([link])
         end
 
         it 'returns an empty Array if the user can not read the group' do
-          expect(Ability).to receive(:allowed?).
-            with(user, :read_group, group).
-            and_return(false)
+          expect(Ability).to receive(:allowed?)
+            .with(user, :read_group, group)
+            .and_return(false)
 
           expect(subject.nodes_visible_to_user(user, [link])).to eq([])
         end
@@ -129,9 +129,9 @@ describe Banzai::ReferenceParser::UserParser, lib: true do
 
             link['data-project'] = other_project.id.to_s
 
-            expect(Ability).to receive(:allowed?).
-              with(user, :read_project, other_project).
-              and_return(true)
+            expect(Ability).to receive(:allowed?)
+              .with(user, :read_project, other_project)
+              .and_return(true)
 
             expect(subject.nodes_visible_to_user(user, [link])).to eq([link])
           end
@@ -141,9 +141,9 @@ describe Banzai::ReferenceParser::UserParser, lib: true do
 
             link['data-project'] = other_project.id.to_s
 
-            expect(Ability).to receive(:allowed?).
-              with(user, :read_project, other_project).
-              and_return(false)
+            expect(Ability).to receive(:allowed?)
+              .with(user, :read_project, other_project)
+              .and_return(false)
 
             expect(subject.nodes_visible_to_user(user, [link])).to eq([])
           end
diff --git a/spec/lib/ci/charts_spec.rb b/spec/lib/ci/charts_spec.rb
index fb6cc398307807511dfb7e4d9fbba836f56aead5..51cbfd2a8483dafffb53c7cc96308dec9d172454 100644
--- a/spec/lib/ci/charts_spec.rb
+++ b/spec/lib/ci/charts_spec.rb
@@ -1,21 +1,21 @@
 require 'spec_helper'
 
 describe Ci::Charts, lib: true do
-  context "build_times" do
+  context "pipeline_times" do
     let(:project) { create(:empty_project) }
-    let(:chart) { Ci::Charts::BuildTime.new(project) }
+    let(:chart) { Ci::Charts::PipelineTime.new(project) }
 
-    subject { chart.build_times }
+    subject { chart.pipeline_times }
 
     before do
       create(:ci_empty_pipeline, project: project, duration: 120)
     end
 
-    it 'returns build times in minutes' do
+    it 'returns pipeline times in minutes' do
       is_expected.to contain_exactly(2)
     end
 
-    it 'handles nil build times' do
+    it 'handles nil pipeline times' do
       create(:ci_empty_pipeline, project: project, duration: nil)
 
       is_expected.to contain_exactly(2, 0)
diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
index af0e7855a9b2838997a5afbae16aad249a2bca86..ef58ef1b0cdd2fdc5cb42277cfa60bebfed429d8 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -598,8 +598,10 @@ module Ci
     describe "Image and service handling" do
       context "when extended docker configuration is used" do
         it "returns image and service when defined" do
-          config = YAML.dump({ image: { name: "ruby:2.1" },
-                               services: ["mysql", { name: "docker:dind", alias: "docker" }],
+          config = YAML.dump({ image: { name: "ruby:2.1", entrypoint: ["/usr/local/bin/init", "run"] },
+                               services: ["mysql", { name: "docker:dind", alias: "docker",
+                                                     entrypoint: ["/usr/local/bin/init", "run"],
+                                                     command: ["/usr/local/bin/init", "run"] }],
                                before_script: ["pwd"],
                                rspec: { script: "rspec" } })
 
@@ -614,8 +616,10 @@ module Ci
             coverage_regex: nil,
             tag_list: [],
             options: {
-                image: { name: "ruby:2.1" },
-                services: [{ name: "mysql" }, { name: "docker:dind", alias: "docker" }]
+                image: { name: "ruby:2.1", entrypoint: ["/usr/local/bin/init", "run"] },
+                services: [{ name: "mysql" },
+                           { name: "docker:dind", alias: "docker", entrypoint: ["/usr/local/bin/init", "run"],
+                             command: ["/usr/local/bin/init", "run"] }]
             },
             allow_failure: false,
             when: "on_success",
@@ -628,8 +632,11 @@ module Ci
           config = YAML.dump({ image: "ruby:2.1",
                                services: ["mysql"],
                                before_script: ["pwd"],
-                               rspec: { image: { name: "ruby:2.5" },
-                                        services: [{ name: "postgresql", alias: "db-pg" }, "docker:dind"], script: "rspec" } })
+                               rspec: { image: { name: "ruby:2.5", entrypoint: ["/usr/local/bin/init", "run"] },
+                                        services: [{ name: "postgresql", alias: "db-pg",
+                                                     entrypoint: ["/usr/local/bin/init", "run"],
+                                                     command: ["/usr/local/bin/init", "run"] }, "docker:dind"],
+                                        script: "rspec" } })
 
           config_processor = GitlabCiYamlProcessor.new(config, path)
 
@@ -642,8 +649,10 @@ module Ci
             coverage_regex: nil,
             tag_list: [],
             options: {
-                image: { name: "ruby:2.5" },
-                services: [{ name: "postgresql", alias: "db-pg" }, { name: "docker:dind" }]
+                image: { name: "ruby:2.5", entrypoint: ["/usr/local/bin/init", "run"] },
+                services: [{ name: "postgresql", alias: "db-pg", entrypoint: ["/usr/local/bin/init", "run"],
+                             command: ["/usr/local/bin/init", "run"] },
+                           { name: "docker:dind" }]
             },
             allow_failure: false,
             when: "on_success",
@@ -869,7 +878,8 @@ module Ci
         expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq(
           paths: ["logs/", "binaries/"],
           untracked: true,
-          key: 'key'
+          key: 'key',
+          policy: 'pull-push'
         )
       end
 
@@ -887,7 +897,8 @@ module Ci
         expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq(
           paths: ["logs/", "binaries/"],
           untracked: true,
-          key: 'key'
+          key: 'key',
+          policy: 'pull-push'
         )
       end
 
@@ -906,7 +917,8 @@ module Ci
         expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq(
           paths: ["test/"],
           untracked: false,
-          key: 'local'
+          key: 'local',
+          policy: 'pull-push'
         )
       end
     end
diff --git a/spec/lib/container_registry/blob_spec.rb b/spec/lib/container_registry/blob_spec.rb
index ab010c6dfebd1ea9ba4b712a542f6c0817cbd849..175fd2e7e130b06a0db8174a8f0cb610f227d7b6 100644
--- a/spec/lib/container_registry/blob_spec.rb
+++ b/spec/lib/container_registry/blob_spec.rb
@@ -72,8 +72,8 @@ describe ContainerRegistry::Blob do
   describe '#data' do
     context 'when locally stored' do
       before do
-        stub_request(:get, 'http://registry.gitlab/v2/group/test/image/blobs/sha256:0123456789012345').
-          to_return(
+        stub_request(:get, 'http://registry.gitlab/v2/group/test/image/blobs/sha256:0123456789012345')
+          .to_return(
             status: 200,
             headers: { 'Content-Type' => 'application/json' },
             body: '{"key":"value"}')
@@ -97,9 +97,9 @@ describe ContainerRegistry::Blob do
 
       context 'for a valid address' do
         before do
-          stub_request(:get, location).
-            with { |request| !request.headers.include?('Authorization') }.
-            to_return(
+          stub_request(:get, location)
+            .with { |request| !request.headers.include?('Authorization') }
+            .to_return(
               status: 200,
               headers: { 'Content-Type' => 'application/json' },
               body: '{"key":"value"}')
diff --git a/spec/lib/container_registry/client_spec.rb b/spec/lib/container_registry/client_spec.rb
index ec03b53338374517c615532b984e100ee9143cdd..3df33f48adbb1e49c126f6986d87e0281e284c19 100644
--- a/spec/lib/container_registry/client_spec.rb
+++ b/spec/lib/container_registry/client_spec.rb
@@ -8,28 +8,28 @@ describe ContainerRegistry::Client do
 
   describe '#blob' do
     it 'GET /v2/:name/blobs/:digest' do
-      stub_request(:get, "http://container-registry/v2/group/test/blobs/sha256:0123456789012345").
-        with(headers: {
+      stub_request(:get, "http://container-registry/v2/group/test/blobs/sha256:0123456789012345")
+        .with(headers: {
                'Accept' => 'application/octet-stream',
                'Authorization' => "bearer #{token}"
-             }).
-        to_return(status: 200, body: "Blob")
+             })
+        .to_return(status: 200, body: "Blob")
 
       expect(client.blob('group/test', 'sha256:0123456789012345')).to eq('Blob')
     end
 
     it 'follows 307 redirect for GET /v2/:name/blobs/:digest' do
-      stub_request(:get, "http://container-registry/v2/group/test/blobs/sha256:0123456789012345").
-        with(headers: {
+      stub_request(:get, "http://container-registry/v2/group/test/blobs/sha256:0123456789012345")
+        .with(headers: {
                'Accept' => 'application/octet-stream',
                'Authorization' => "bearer #{token}"
-             }).
-        to_return(status: 307, body: "", headers: { Location: 'http://redirected' })
+             })
+        .to_return(status: 307, body: "", headers: { Location: 'http://redirected' })
       # We should probably use hash_excluding here, but that requires an update to WebMock:
       # https://github.com/bblimke/webmock/blob/master/lib/webmock/matchers/hash_excluding_matcher.rb
-      stub_request(:get, "http://redirected/").
-        with { |request| !request.headers.include?('Authorization') }.
-        to_return(status: 200, body: "Successfully redirected")
+      stub_request(:get, "http://redirected/")
+        .with { |request| !request.headers.include?('Authorization') }
+        .to_return(status: 200, body: "Successfully redirected")
 
       response = client.blob('group/test', 'sha256:0123456789012345')
 
diff --git a/spec/lib/container_registry/tag_spec.rb b/spec/lib/container_registry/tag_spec.rb
index f8fffbdca4148b9a713d9ed5eded43de18bb08ee..cb4ae3be525b8c95803b99fe7369a6a877441301 100644
--- a/spec/lib/container_registry/tag_spec.rb
+++ b/spec/lib/container_registry/tag_spec.rb
@@ -60,9 +60,9 @@ describe ContainerRegistry::Tag do
   context 'manifest processing' do
     context 'schema v1' do
       before do
-        stub_request(:get, 'http://registry.gitlab/v2/group/test/manifests/tag').
-          with(headers: headers).
-          to_return(
+        stub_request(:get, 'http://registry.gitlab/v2/group/test/manifests/tag')
+          .with(headers: headers)
+          .to_return(
             status: 200,
             body: File.read(Rails.root + 'spec/fixtures/container_registry/tag_manifest_1.json'),
             headers: { 'Content-Type' => 'application/vnd.docker.distribution.manifest.v1+prettyjws' })
@@ -97,9 +97,9 @@ describe ContainerRegistry::Tag do
 
     context 'schema v2' do
       before do
-        stub_request(:get, 'http://registry.gitlab/v2/group/test/manifests/tag').
-          with(headers: headers).
-          to_return(
+        stub_request(:get, 'http://registry.gitlab/v2/group/test/manifests/tag')
+          .with(headers: headers)
+          .to_return(
             status: 200,
             body: File.read(Rails.root + 'spec/fixtures/container_registry/tag_manifest.json'),
             headers: { 'Content-Type' => 'application/vnd.docker.distribution.manifest.v2+json' })
@@ -134,9 +134,9 @@ describe ContainerRegistry::Tag do
 
         context 'when locally stored' do
           before do
-            stub_request(:get, 'http://registry.gitlab/v2/group/test/blobs/sha256:d7a513a663c1a6dcdba9ed832ca53c02ac2af0c333322cd6ca92936d1d9917ac').
-              with(headers: { 'Accept' => 'application/octet-stream' }).
-              to_return(
+            stub_request(:get, 'http://registry.gitlab/v2/group/test/blobs/sha256:d7a513a663c1a6dcdba9ed832ca53c02ac2af0c333322cd6ca92936d1d9917ac')
+              .with(headers: { 'Accept' => 'application/octet-stream' })
+              .to_return(
                 status: 200,
                 body: File.read(Rails.root + 'spec/fixtures/container_registry/config_blob.json'))
           end
@@ -146,14 +146,14 @@ describe ContainerRegistry::Tag do
 
         context 'when externally stored' do
           before do
-            stub_request(:get, 'http://registry.gitlab/v2/group/test/blobs/sha256:d7a513a663c1a6dcdba9ed832ca53c02ac2af0c333322cd6ca92936d1d9917ac').
-              with(headers: { 'Accept' => 'application/octet-stream' }).
-              to_return(
+            stub_request(:get, 'http://registry.gitlab/v2/group/test/blobs/sha256:d7a513a663c1a6dcdba9ed832ca53c02ac2af0c333322cd6ca92936d1d9917ac')
+              .with(headers: { 'Accept' => 'application/octet-stream' })
+              .to_return(
                 status: 307,
                 headers: { 'Location' => 'http://external.com/blob/file' })
 
-            stub_request(:get, 'http://external.com/blob/file').
-              to_return(
+            stub_request(:get, 'http://external.com/blob/file')
+              .to_return(
                 status: 200,
                 body: File.read(Rails.root + 'spec/fixtures/container_registry/config_blob.json'))
           end
diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb
index 2b26a3185831eb3dffef7347c921383019059ba1..f2132d485abdf7cc72504902c642d06ace8ba2bc 100644
--- a/spec/lib/extracts_path_spec.rb
+++ b/spec/lib/extracts_path_spec.rb
@@ -14,8 +14,8 @@ describe ExtractsPath, lib: true do
     repo = double(ref_names: ['master', 'foo/bar/baz', 'v1.0.0', 'v2.0.0',
                               'release/app', 'release/app/v1.0.0'])
     allow(project).to receive(:repository).and_return(repo)
-    allow(project).to receive(:path_with_namespace).
-      and_return('gitlab/gitlab-ci')
+    allow(project).to receive(:path_with_namespace)
+      .and_return('gitlab/gitlab-ci')
     allow(request).to receive(:format=)
   end
 
diff --git a/spec/lib/feature_spec.rb b/spec/lib/feature_spec.rb
index 1d92a5cb33fc445f93f64e396f12b956d60eef70..5cc3a3745e441c0e35030d11c3b5992bfdc8a66e 100644
--- a/spec/lib/feature_spec.rb
+++ b/spec/lib/feature_spec.rb
@@ -6,8 +6,8 @@ describe Feature, lib: true do
     let(:key) { 'my_feature' }
 
     it 'returns the Flipper feature' do
-      expect_any_instance_of(Flipper::DSL).to receive(:feature).with(key).
-        and_return(feature)
+      expect_any_instance_of(Flipper::DSL).to receive(:feature).with(key)
+        .and_return(feature)
 
       expect(described_class.get(key)).to be(feature)
     end
@@ -17,8 +17,8 @@ describe Feature, lib: true do
     let(:features) { Set.new }
 
     it 'returns the Flipper features as an array' do
-      expect_any_instance_of(Flipper::DSL).to receive(:features).
-        and_return(features)
+      expect_any_instance_of(Flipper::DSL).to receive(:features)
+        .and_return(features)
 
       expect(described_class.all).to eq(features.to_a)
     end
diff --git a/spec/lib/gitlab/background_migration_spec.rb b/spec/lib/gitlab/background_migration_spec.rb
index f2073b9bcb354d06f2f1cd7f9027a814e70a6b4f..64f82fe27b2adb11fbedd56936ad6a5552dbec6a 100644
--- a/spec/lib/gitlab/background_migration_spec.rb
+++ b/spec/lib/gitlab/background_migration_spec.rb
@@ -5,9 +5,9 @@ describe Gitlab::BackgroundMigration do
     it 'steals jobs from a queue' do
       queue = [double(:job, args: ['Foo', [10, 20]])]
 
-      allow(Sidekiq::Queue).to receive(:new).
-        with(BackgroundMigrationWorker.sidekiq_options['queue']).
-        and_return(queue)
+      allow(Sidekiq::Queue).to receive(:new)
+        .with(BackgroundMigrationWorker.sidekiq_options['queue'])
+        .and_return(queue)
 
       expect(queue[0]).to receive(:delete)
 
@@ -19,9 +19,9 @@ describe Gitlab::BackgroundMigration do
     it 'does not steal jobs for a different migration' do
       queue = [double(:job, args: ['Foo', [10, 20]])]
 
-      allow(Sidekiq::Queue).to receive(:new).
-        with(BackgroundMigrationWorker.sidekiq_options['queue']).
-        and_return(queue)
+      allow(Sidekiq::Queue).to receive(:new)
+        .with(BackgroundMigrationWorker.sidekiq_options['queue'])
+        .and_return(queue)
 
       expect(described_class).not_to receive(:perform)
 
@@ -36,9 +36,9 @@ describe Gitlab::BackgroundMigration do
       instance = double(:instance)
       klass = double(:klass, new: instance)
 
-      expect(described_class).to receive(:const_get).
-        with('Foo').
-        and_return(klass)
+      expect(described_class).to receive(:const_get)
+        .with('Foo')
+        .and_return(klass)
 
       expect(instance).to receive(:perform).with(10, 20)
 
diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
index a7ee7f53a6b23d2d47267855bc0b4a6b75e105dc..d8beb05601c11b6ec1f46ac1b90beb90676fae20 100644
--- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
@@ -86,11 +86,9 @@ describe Gitlab::BitbucketImport::Importer, lib: true do
                   headers: { "Content-Type" => "application/json" },
                   body: issues_statuses_sample_data.to_json)
 
-      stub_request(:get, "https://api.bitbucket.org/2.0/repositories/namespace/repo?pagelen=50&sort=created_on").
-        with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer', 'User-Agent' => 'Faraday v0.9.2' }).
-        to_return(status: 200,
-                  body: "",
-                  headers: {})
+      stub_request(:get, "https://api.bitbucket.org/2.0/repositories/namespace/repo?pagelen=50&sort=created_on")
+        .with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer', 'User-Agent' => 'Faraday v0.9.2' })
+        .to_return(status: 200, body: "", headers: {})
 
       sample_issues_statuses.each_with_index do |issue, index|
         stub_request(
diff --git a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
index cfb5cba054ec0079b77d47f45ad82a96841f14fe..07db6c3a640bbf8273fd6a8051f4b958781a3f9f 100644
--- a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
+++ b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
@@ -37,11 +37,11 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :redis do
             loaded_from_cache: false
           )
 
-          expect(described_class).to receive(:new).
-                                       with(project_without_status,
+          expect(described_class).to receive(:new)
+                                       .with(project_without_status,
                                             pipeline_info: empty_status,
-                                            loaded_from_cache: false).
-                                       and_return(fake_pipeline)
+                                            loaded_from_cache: false)
+                                       .and_return(fake_pipeline)
           expect(fake_pipeline).to receive(:load_from_project)
           expect(fake_pipeline).to receive(:store_in_cache)
 
@@ -112,12 +112,12 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :redis do
       pipeline = build_stubbed(:ci_pipeline,
                                sha: '123456', status: 'success', ref: 'master')
       fake_status = double
-      expect(described_class).to receive(:new).
-                                   with(pipeline.project,
+      expect(described_class).to receive(:new)
+                                   .with(pipeline.project,
                                         pipeline_info: {
                                           sha: '123456', status: 'success', ref: 'master'
-                                        }).
-                                   and_return(fake_status)
+                                        })
+                                   .and_return(fake_status)
 
       expect(fake_status).to receive(:store_in_cache_if_needed)
 
diff --git a/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb b/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
index eea01f91879ec97d2ded8e56b17119ff4b74e6c5..6a52ae01b2fc258cc96ea1687fdeb3d943f64bea 100644
--- a/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
+++ b/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
@@ -33,8 +33,8 @@ describe Gitlab::Ci::Build::Artifacts::Metadata do
       subject { metadata('other_artifacts_0.1.2/').find_entries! }
 
       it 'matches correct paths' do
-        expect(subject.keys).
-          to contain_exactly 'other_artifacts_0.1.2/',
+        expect(subject.keys)
+          .to contain_exactly 'other_artifacts_0.1.2/',
                              'other_artifacts_0.1.2/doc_sample.txt',
                              'other_artifacts_0.1.2/another-subdirectory/'
       end
@@ -44,8 +44,8 @@ describe Gitlab::Ci::Build::Artifacts::Metadata do
       subject { metadata('other_artifacts_0.1.2/another-subdirectory/').find_entries! }
 
       it 'matches correct paths' do
-        expect(subject.keys).
-          to contain_exactly 'other_artifacts_0.1.2/another-subdirectory/',
+        expect(subject.keys)
+          .to contain_exactly 'other_artifacts_0.1.2/another-subdirectory/',
                              'other_artifacts_0.1.2/another-subdirectory/empty_directory/',
                              'other_artifacts_0.1.2/another-subdirectory/banana_sample.gif'
       end
@@ -55,8 +55,8 @@ describe Gitlab::Ci::Build::Artifacts::Metadata do
       subject { metadata('other_artifacts_0.1.2/', recursive: true).find_entries! }
 
       it 'matches correct paths' do
-        expect(subject.keys).
-          to contain_exactly 'other_artifacts_0.1.2/',
+        expect(subject.keys)
+          .to contain_exactly 'other_artifacts_0.1.2/',
                              'other_artifacts_0.1.2/doc_sample.txt',
                              'other_artifacts_0.1.2/another-subdirectory/',
                              'other_artifacts_0.1.2/another-subdirectory/empty_directory/',
diff --git a/spec/lib/gitlab/ci/config/entry/cache_spec.rb b/spec/lib/gitlab/ci/config/entry/cache_spec.rb
index 878b1d6b862d9720755af21e66b7161a62063e39..8f711e02f9b47f840166729f2cb5a50d96ddfbbf 100644
--- a/spec/lib/gitlab/ci/config/entry/cache_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/cache_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Gitlab::Ci::Config::Entry::Cache do
-  let(:entry) { described_class.new(config) }
+  subject(:entry) { described_class.new(config) }
 
   describe 'validations' do
     before do
@@ -9,22 +9,44 @@ describe Gitlab::Ci::Config::Entry::Cache do
     end
 
     context 'when entry config value is correct' do
+      let(:policy) { nil }
+
       let(:config) do
         { key: 'some key',
           untracked: true,
-          paths: ['some/path/'] }
+          paths: ['some/path/'],
+          policy: policy }
       end
 
       describe '#value' do
         it 'returns hash value' do
-          expect(entry.value).to eq config
+          expect(entry.value).to eq(key: 'some key', untracked: true, paths: ['some/path/'], policy: 'pull-push')
         end
       end
 
       describe '#valid?' do
-        it 'is valid' do
-          expect(entry).to be_valid
-        end
+        it { is_expected.to be_valid }
+      end
+
+      context 'policy is pull-push' do
+        let(:policy) { 'pull-push' }
+
+        it { is_expected.to be_valid }
+        it { expect(entry.value).to include(policy: 'pull-push') }
+      end
+
+      context 'policy is push' do
+        let(:policy) { 'push' }
+
+        it { is_expected.to be_valid }
+        it { expect(entry.value).to include(policy: 'push') }
+      end
+
+      context 'policy is pull' do
+        let(:policy) { 'pull' }
+
+        it { is_expected.to be_valid }
+        it { expect(entry.value).to include(policy: 'pull') }
       end
 
       context 'when key is missing' do
@@ -44,12 +66,20 @@ describe Gitlab::Ci::Config::Entry::Cache do
 
     context 'when entry value is not correct' do
       describe '#errors' do
+        subject { entry.errors }
         context 'when is not a hash' do
           let(:config) { 'ls' }
 
           it 'reports errors with config value' do
-            expect(entry.errors)
-              .to include 'cache config should be a hash'
+            is_expected.to include 'cache config should be a hash'
+          end
+        end
+
+        context 'when policy is unknown' do
+          let(:config) { { policy: "unknown" } }
+
+          it 'reports error' do
+            is_expected.to include('cache policy should be pull-push, push, or pull')
           end
         end
 
@@ -57,8 +87,7 @@ describe Gitlab::Ci::Config::Entry::Cache do
           let(:config) { { key: 1 } }
 
           it 'reports error with descendants' do
-            expect(entry.errors)
-              .to include 'key config should be a string or symbol'
+            is_expected.to include 'key config should be a string or symbol'
           end
         end
 
@@ -66,8 +95,7 @@ describe Gitlab::Ci::Config::Entry::Cache do
           let(:config) { { invalid: true } }
 
           it 'reports error with descendants' do
-            expect(entry.errors)
-              .to include 'cache config contains unknown keys: invalid'
+            is_expected.to include 'cache config contains unknown keys: invalid'
           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 293f112b2b0b08d2411c42fa9d7c1111704eef68..1860ed79bfd4cfa82a3b4ffe7768f06996793c8c 100644
--- a/spec/lib/gitlab/ci/config/entry/global_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/global_spec.rb
@@ -143,7 +143,7 @@ describe Gitlab::Ci::Config::Entry::Global do
         describe '#cache_value' do
           it 'returns cache configuration' do
             expect(global.cache_value)
-              .to eq(key: 'k', untracked: true, paths: ['public/'])
+              .to eq(key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push')
           end
         end
 
@@ -157,7 +157,7 @@ describe Gitlab::Ci::Config::Entry::Global do
                        image: { name: 'ruby:2.2' },
                        services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
                        stage: 'test',
-                       cache: { key: 'k', untracked: true, paths: ['public/'] },
+                       cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push' },
                        variables: { 'VAR' => 'value' },
                        ignore: false,
                        after_script: ['make clean'] },
@@ -168,7 +168,7 @@ describe Gitlab::Ci::Config::Entry::Global do
                          image: { name: 'ruby:2.2' },
                          services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
                          stage: 'test',
-                         cache: { key: 'k', untracked: true, paths: ['public/'] },
+                         cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push' },
                          variables: {},
                          ignore: false,
                          after_script: ['make clean'] }
@@ -212,7 +212,7 @@ describe Gitlab::Ci::Config::Entry::Global do
 
       describe '#cache_value' do
         it 'returns correct cache definition' do
-          expect(global.cache_value).to eq(key: 'a')
+          expect(global.cache_value).to eq(key: 'a', policy: 'pull-push')
         end
       end
     end
diff --git a/spec/lib/gitlab/ci/config/entry/image_spec.rb b/spec/lib/gitlab/ci/config/entry/image_spec.rb
index bca22e39500f0f61eef77530590b45e11e5f52b7..1a4d9ed5517167a4af06933c4d05944d8ed0d7d3 100644
--- a/spec/lib/gitlab/ci/config/entry/image_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/image_spec.rb
@@ -38,7 +38,7 @@ describe Gitlab::Ci::Config::Entry::Image do
   end
 
   context 'when configuration is a hash' do
-    let(:config) { { name: 'ruby:2.2', entrypoint: '/bin/sh' } }
+    let(:config) { { name: 'ruby:2.2', entrypoint: %w(/bin/sh run) } }
 
     describe '#value' do
       it 'returns image hash' do
@@ -66,7 +66,7 @@ describe Gitlab::Ci::Config::Entry::Image do
 
     describe '#entrypoint' do
       it "returns image's entrypoint" do
-        expect(entry.entrypoint).to eq '/bin/sh'
+        expect(entry.entrypoint).to eq %w(/bin/sh run)
       end
     end
   end
diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb
index 92cba689f47161dcb34ed563df43f92fee9fef19..c5cad887b64c51031eb1f0cbe305c9d255b20894 100644
--- a/spec/lib/gitlab/ci/config/entry/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb
@@ -109,7 +109,7 @@ describe Gitlab::Ci::Config::Entry::Job do
 
       it 'overrides global config' do
         expect(entry[:image].value).to eq(name: 'some_image')
-        expect(entry[:cache].value).to eq(key: 'test')
+        expect(entry[:cache].value).to eq(key: 'test', policy: 'pull-push')
       end
     end
 
@@ -123,7 +123,7 @@ describe Gitlab::Ci::Config::Entry::Job do
 
       it 'uses config from global entry' do
         expect(entry[:image].value).to eq 'specified'
-        expect(entry[:cache].value).to eq(key: 'test')
+        expect(entry[:cache].value).to eq(key: 'test', policy: 'pull-push')
       end
     end
   end
diff --git a/spec/lib/gitlab/ci/config/entry/service_spec.rb b/spec/lib/gitlab/ci/config/entry/service_spec.rb
index 7202fe525e426767613a29f063e85144d2badad8..9ebf947a751a1ba260bb0af742c79e03db25515b 100644
--- a/spec/lib/gitlab/ci/config/entry/service_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/service_spec.rb
@@ -43,7 +43,7 @@ describe Gitlab::Ci::Config::Entry::Service do
 
   context 'when configuration is a hash' do
     let(:config) do
-      { name: 'postgresql:9.5', alias: 'db', command: 'cmd', entrypoint: '/bin/sh' }
+      { name: 'postgresql:9.5', alias: 'db', command: %w(cmd run), entrypoint: %w(/bin/sh run) }
     end
 
     describe '#valid?' do
@@ -72,13 +72,13 @@ describe Gitlab::Ci::Config::Entry::Service do
 
     describe '#command' do
       it "returns service's command" do
-        expect(entry.command).to eq 'cmd'
+        expect(entry.command).to eq %w(cmd run)
       end
     end
 
     describe '#entrypoint' do
       it "returns service's entrypoint" do
-        expect(entry.entrypoint).to eq '/bin/sh'
+        expect(entry.entrypoint).to eq %w(/bin/sh run)
       end
     end
   end
diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb
index 97af1c2523dc3caf0075eb90d413875ef0b60152..fe988266ae3c9da86c575eda7a256e45d7e1969b 100644
--- a/spec/lib/gitlab/closing_issue_extractor_spec.rb
+++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb
@@ -276,7 +276,7 @@ describe Gitlab::ClosingIssueExtractor, lib: true do
 
     context "with a cross-project URL" do
       it do
-        message = "Closes #{urls.namespace_project_issue_url(issue2.project.namespace, issue2.project, issue2)}"
+        message = "Closes #{urls.project_issue_url(issue2.project, issue2)}"
         expect(subject.closed_by_message(message)).to eq([issue2])
       end
     end
@@ -292,7 +292,7 @@ describe Gitlab::ClosingIssueExtractor, lib: true do
 
     context "with an invalid URL" do
       it do
-        message = "Closes https://google.com#{urls.namespace_project_issue_path(issue2.project.namespace, issue2.project, issue2)}"
+        message = "Closes https://google.com#{urls.project_issue_path(issue2.project, issue2)}"
         expect(subject.closed_by_message(message)).to eq([])
       end
     end
@@ -306,58 +306,58 @@ describe Gitlab::ClosingIssueExtractor, lib: true do
       it 'fetches issues in single line message' do
         message = "Closes #{reference} and fix #{reference2}"
 
-        expect(subject.closed_by_message(message)).
-            to match_array([issue, other_issue])
+        expect(subject.closed_by_message(message))
+            .to match_array([issue, other_issue])
       end
 
       it 'fetches comma-separated issues references in single line message' do
         message = "Closes #{reference}, closes #{reference2}"
 
-        expect(subject.closed_by_message(message)).
-            to match_array([issue, other_issue])
+        expect(subject.closed_by_message(message))
+            .to match_array([issue, other_issue])
       end
 
       it 'fetches comma-separated issues numbers in single line message' do
         message = "Closes #{reference}, #{reference2} and #{reference3}"
 
-        expect(subject.closed_by_message(message)).
-            to match_array([issue, other_issue, third_issue])
+        expect(subject.closed_by_message(message))
+            .to match_array([issue, other_issue, third_issue])
       end
 
       it 'fetches issues in multi-line message' do
         message = "Awesome commit (closes #{reference})\nAlso fixes #{reference2}"
 
-        expect(subject.closed_by_message(message)).
-            to match_array([issue, other_issue])
+        expect(subject.closed_by_message(message))
+            .to match_array([issue, other_issue])
       end
 
       it 'fetches issues in hybrid message' do
         message = "Awesome commit (closes #{reference})\n"\
                   "Also fixing issues #{reference2}, #{reference3} and #4"
 
-        expect(subject.closed_by_message(message)).
-            to match_array([issue, other_issue, third_issue])
+        expect(subject.closed_by_message(message))
+            .to match_array([issue, other_issue, third_issue])
       end
 
       it "fetches cross-project references" do
         message = "Closes #{reference} and #{cross_reference}"
 
-        expect(subject.closed_by_message(message)).
-            to match_array([issue, issue2])
+        expect(subject.closed_by_message(message))
+            .to match_array([issue, issue2])
       end
 
       it "fetches cross-project URL references" do
-        message = "Closes #{urls.namespace_project_issue_url(issue2.project.namespace, issue2.project, issue2)} and #{reference}"
+        message = "Closes #{urls.project_issue_url(issue2.project, issue2)} and #{reference}"
 
-        expect(subject.closed_by_message(message)).
-            to match_array([issue, issue2])
+        expect(subject.closed_by_message(message))
+            .to match_array([issue, issue2])
       end
 
       it "ignores invalid cross-project URL references" do
-        message = "Closes https://google.com#{urls.namespace_project_issue_path(issue2.project.namespace, issue2.project, issue2)} and #{reference}"
+        message = "Closes https://google.com#{urls.project_issue_path(issue2.project, issue2)} and #{reference}"
 
-        expect(subject.closed_by_message(message)).
-            to match_array([issue])
+        expect(subject.closed_by_message(message))
+            .to match_array([issue])
       end
     end
   end
diff --git a/spec/lib/gitlab/conflict/file_spec.rb b/spec/lib/gitlab/conflict/file_spec.rb
index 780ac0ad97ed433e8505c4f3ffd9c8138303b90e..585eeb77bd5c46e2428cf5e0e30593891cd8c201 100644
--- a/spec/lib/gitlab/conflict/file_spec.rb
+++ b/spec/lib/gitlab/conflict/file_spec.rb
@@ -43,8 +43,8 @@ describe Gitlab::Conflict::File, lib: true do
       end
 
       it 'returns a file containing only the chosen parts of the resolved sections' do
-        expect(resolved_lines.chunk { |line| line.type || 'both' }.map(&:first)).
-          to eq(%w(both new both old both new both))
+        expect(resolved_lines.chunk { |line| line.type || 'both' }.map(&:first))
+          .to eq(%w(both new both old both new both))
       end
     end
 
@@ -52,14 +52,14 @@ describe Gitlab::Conflict::File, lib: true do
       empty_hash = section_keys.map { |key| [key, nil] }.to_h
       invalid_hash = section_keys.map { |key| [key, 'invalid'] }.to_h
 
-      expect { conflict_file.resolve_lines({}) }.
-        to raise_error(Gitlab::Conflict::File::MissingResolution)
+      expect { conflict_file.resolve_lines({}) }
+        .to raise_error(Gitlab::Conflict::File::MissingResolution)
 
-      expect { conflict_file.resolve_lines(empty_hash) }.
-        to raise_error(Gitlab::Conflict::File::MissingResolution)
+      expect { conflict_file.resolve_lines(empty_hash) }
+        .to raise_error(Gitlab::Conflict::File::MissingResolution)
 
-      expect { conflict_file.resolve_lines(invalid_hash) }.
-        to raise_error(Gitlab::Conflict::File::MissingResolution)
+      expect { conflict_file.resolve_lines(invalid_hash) }
+        .to raise_error(Gitlab::Conflict::File::MissingResolution)
     end
   end
 
@@ -250,8 +250,8 @@ FILE
 
   describe '#as_json' do
     it 'includes the blob path for the file' do
-      expect(conflict_file.as_json[:blob_path]).
-        to eq("/#{project.full_path}/blob/#{our_commit.oid}/files/ruby/regex.rb")
+      expect(conflict_file.as_json[:blob_path])
+        .to eq("/#{project.full_path}/blob/#{our_commit.oid}/files/ruby/regex.rb")
     end
 
     it 'includes the blob icon for the file' do
@@ -264,8 +264,8 @@ FILE
       end
 
       it 'includes the detected language of the conflict file' do
-        expect(conflict_file.as_json(full_content: true)[:blob_ace_mode]).
-          to eq('ruby')
+        expect(conflict_file.as_json(full_content: true)[:blob_ace_mode])
+          .to eq('ruby')
       end
     end
   end
diff --git a/spec/lib/gitlab/conflict/parser_spec.rb b/spec/lib/gitlab/conflict/parser_spec.rb
index 2570f95dd21be4bc396c4df1feb44e7233f697ae..aed57b75789ce242d796fec8248bc4bf65603075 100644
--- a/spec/lib/gitlab/conflict/parser_spec.rb
+++ b/spec/lib/gitlab/conflict/parser_spec.rb
@@ -122,18 +122,18 @@ CONFLICT
     context 'when the file contents include conflict delimiters' do
       context 'when there is a non-start delimiter first' do
         it 'raises UnexpectedDelimiter when there is a middle delimiter first' do
-          expect { parse_text('=======') }.
-            to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
+          expect { parse_text('=======') }
+            .to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
         end
 
         it 'raises UnexpectedDelimiter when there is an end delimiter first' do
-          expect { parse_text('>>>>>>> README.md') }.
-            to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
+          expect { parse_text('>>>>>>> README.md') }
+            .to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
         end
 
         it 'does not raise when there is an end delimiter for a different path first' do
-          expect { parse_text('>>>>>>> some-other-path.md') }.
-            not_to raise_error
+          expect { parse_text('>>>>>>> some-other-path.md') }
+            .not_to raise_error
         end
       end
 
@@ -142,18 +142,18 @@ CONFLICT
         let(:end_text) { "\n=======\n>>>>>>> README.md" }
 
         it 'raises UnexpectedDelimiter when it is followed by an end delimiter' do
-          expect { parse_text(start_text + '>>>>>>> README.md' + end_text) }.
-            to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
+          expect { parse_text(start_text + '>>>>>>> README.md' + end_text) }
+            .to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
         end
 
         it 'raises UnexpectedDelimiter when it is followed by another start delimiter' do
-          expect { parse_text(start_text + start_text + end_text) }.
-            to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
+          expect { parse_text(start_text + start_text + end_text) }
+            .to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
         end
 
         it 'does not raise when it is followed by a start delimiter for a different path' do
-          expect { parse_text(start_text + '>>>>>>> some-other-path.md' + end_text) }.
-            not_to raise_error
+          expect { parse_text(start_text + '>>>>>>> some-other-path.md' + end_text) }
+            .not_to raise_error
         end
       end
 
@@ -162,59 +162,59 @@ CONFLICT
         let(:end_text) { "\n>>>>>>> README.md" }
 
         it 'raises UnexpectedDelimiter when it is followed by another middle delimiter' do
-          expect { parse_text(start_text + '=======' + end_text) }.
-            to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
+          expect { parse_text(start_text + '=======' + end_text) }
+            .to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
         end
 
         it 'raises UnexpectedDelimiter when it is followed by a start delimiter' do
-          expect { parse_text(start_text + start_text + end_text) }.
-            to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
+          expect { parse_text(start_text + start_text + end_text) }
+            .to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
         end
 
         it 'does not raise when it is followed by a start delimiter for another path' do
-          expect { parse_text(start_text + '<<<<<<< some-other-path.md' + end_text) }.
-            not_to raise_error
+          expect { parse_text(start_text + '<<<<<<< some-other-path.md' + end_text) }
+            .not_to raise_error
         end
       end
 
       it 'raises MissingEndDelimiter when there is no end delimiter at the end' do
         start_text = "<<<<<<< README.md\n=======\n"
 
-        expect { parse_text(start_text) }.
-          to raise_error(Gitlab::Conflict::Parser::MissingEndDelimiter)
+        expect { parse_text(start_text) }
+          .to raise_error(Gitlab::Conflict::Parser::MissingEndDelimiter)
 
-        expect { parse_text(start_text + '>>>>>>> some-other-path.md') }.
-          to raise_error(Gitlab::Conflict::Parser::MissingEndDelimiter)
+        expect { parse_text(start_text + '>>>>>>> some-other-path.md') }
+          .to raise_error(Gitlab::Conflict::Parser::MissingEndDelimiter)
       end
     end
 
     context 'other file types' do
       it 'raises UnmergeableFile when lines is blank, indicating a binary file' do
-        expect { parse_text('') }.
-          to raise_error(Gitlab::Conflict::Parser::UnmergeableFile)
+        expect { parse_text('') }
+          .to raise_error(Gitlab::Conflict::Parser::UnmergeableFile)
 
-        expect { parse_text(nil) }.
-          to raise_error(Gitlab::Conflict::Parser::UnmergeableFile)
+        expect { parse_text(nil) }
+          .to raise_error(Gitlab::Conflict::Parser::UnmergeableFile)
       end
 
       it 'raises UnmergeableFile when the file is over 200 KB' do
-        expect { parse_text('a' * 204801) }.
-          to raise_error(Gitlab::Conflict::Parser::UnmergeableFile)
+        expect { parse_text('a' * 204801) }
+          .to raise_error(Gitlab::Conflict::Parser::UnmergeableFile)
       end
 
       # All text from Rugged has an encoding of ASCII_8BIT, so force that in
       # these strings.
       context 'when the file contains UTF-8 characters' do
         it 'does not raise' do
-          expect { parse_text("Espa\xC3\xB1a".force_encoding(Encoding::ASCII_8BIT)) }.
-            not_to raise_error
+          expect { parse_text("Espa\xC3\xB1a".force_encoding(Encoding::ASCII_8BIT)) }
+            .not_to raise_error
         end
       end
 
       context 'when the file contains non-UTF-8 characters' do
         it 'raises UnsupportedEncoding' do
-          expect { parse_text("a\xC4\xFC".force_encoding(Encoding::ASCII_8BIT)) }.
-            to raise_error(Gitlab::Conflict::Parser::UnsupportedEncoding)
+          expect { parse_text("a\xC4\xFC".force_encoding(Encoding::ASCII_8BIT)) }
+            .to raise_error(Gitlab::Conflict::Parser::UnsupportedEncoding)
         end
       end
     end
diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb
index fda39d7861009e9225d6b91eca20c053d9f40d49..a566f24f6a6a31d69769ae016bb2344d7804bf7d 100644
--- a/spec/lib/gitlab/current_settings_spec.rb
+++ b/spec/lib/gitlab/current_settings_spec.rb
@@ -32,6 +32,37 @@ describe Gitlab::CurrentSettings do
 
         expect(current_application_settings).to be_a(ApplicationSetting)
       end
+
+      context 'with migrations pending' do
+        before do
+          expect(ActiveRecord::Migrator).to receive(:needs_migration?).and_return(true)
+        end
+
+        it 'returns an in-memory ApplicationSetting object' do
+          settings = current_application_settings
+
+          expect(settings).to be_a(OpenStruct)
+          expect(settings.sign_in_enabled?).to eq(settings.sign_in_enabled)
+          expect(settings.sign_up_enabled?).to eq(settings.sign_up_enabled)
+        end
+
+        it 'uses the existing database settings and falls back to defaults' do
+          db_settings = create(:application_setting,
+                               home_page_url: 'http://mydomain.com',
+                               signup_enabled: false)
+          settings = current_application_settings
+          app_defaults = ApplicationSetting.last
+
+          expect(settings).to be_a(OpenStruct)
+          expect(settings.home_page_url).to eq(db_settings.home_page_url)
+          expect(settings.signup_enabled?).to be_falsey
+          expect(settings.signup_enabled).to be_falsey
+
+          # Check that unspecified values use the defaults
+          settings.reject! { |key, _| [:home_page_url, :signup_enabled].include? key }
+          settings.each { |key, _| expect(settings[key]).to eq(app_defaults[key]) }
+        end
+      end
     end
 
     context 'with DB unavailable' do
diff --git a/spec/lib/gitlab/data_builder/push_spec.rb b/spec/lib/gitlab/data_builder/push_spec.rb
index e59cba35b2feb737bfe06cc20f4d2c5113eccec1..73936969832fb8ff5d200f49fa765f1bcd7f78c7 100644
--- a/spec/lib/gitlab/data_builder/push_spec.rb
+++ b/spec/lib/gitlab/data_builder/push_spec.rb
@@ -47,8 +47,8 @@ describe Gitlab::DataBuilder::Push, lib: true do
     include_examples 'deprecated repository hook data'
 
     it 'does not raise an error when given nil commits' do
-      expect { described_class.build(spy, spy, spy, spy, spy, nil) }.
-        not_to raise_error
+      expect { described_class.build(spy, spy, spy, spy, spy, nil) }
+        .not_to raise_error
     end
   end
 end
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index 30aa463faf88a9617092e12e1302cba302849140..4259be3f522240e97c630c9ccac868cbcbb52e3f 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -57,15 +57,15 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
         end
 
         it 'creates the index concurrently' do
-          expect(model).to receive(:add_index).
-            with(:users, :foo, algorithm: :concurrently)
+          expect(model).to receive(:add_index)
+            .with(:users, :foo, algorithm: :concurrently)
 
           model.add_concurrent_index(:users, :foo)
         end
 
         it 'creates unique index concurrently' do
-          expect(model).to receive(:add_index).
-            with(:users, :foo, { algorithm: :concurrently, unique: true })
+          expect(model).to receive(:add_index)
+            .with(:users, :foo, { algorithm: :concurrently, unique: true })
 
           model.add_concurrent_index(:users, :foo, unique: true)
         end
@@ -75,8 +75,8 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
         it 'creates a regular index' do
           expect(Gitlab::Database).to receive(:postgresql?).and_return(false)
 
-          expect(model).to receive(:add_index).
-            with(:users, :foo, {})
+          expect(model).to receive(:add_index)
+            .with(:users, :foo, {})
 
           model.add_concurrent_index(:users, :foo)
         end
@@ -87,8 +87,8 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
       it 'raises RuntimeError' do
         expect(model).to receive(:transaction_open?).and_return(true)
 
-        expect { model.add_concurrent_index(:users, :foo) }.
-          to raise_error(RuntimeError)
+        expect { model.add_concurrent_index(:users, :foo) }
+          .to raise_error(RuntimeError)
       end
     end
   end
@@ -106,15 +106,15 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
         end
 
         it 'removes the index concurrently by column name' do
-          expect(model).to receive(:remove_index).
-            with(:users, { algorithm: :concurrently, column: :foo })
+          expect(model).to receive(:remove_index)
+            .with(:users, { algorithm: :concurrently, column: :foo })
 
           model.remove_concurrent_index(:users, :foo)
         end
 
         it 'removes the index concurrently by index name' do
-          expect(model).to receive(:remove_index).
-            with(:users, { algorithm: :concurrently, name: "index_x_by_y" })
+          expect(model).to receive(:remove_index)
+            .with(:users, { algorithm: :concurrently, name: "index_x_by_y" })
 
           model.remove_concurrent_index_by_name(:users, "index_x_by_y")
         end
@@ -124,8 +124,8 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
         it 'removes an index' do
           expect(Gitlab::Database).to receive(:postgresql?).and_return(false)
 
-          expect(model).to receive(:remove_index).
-            with(:users, { column: :foo })
+          expect(model).to receive(:remove_index)
+            .with(:users, { column: :foo })
 
           model.remove_concurrent_index(:users, :foo)
         end
@@ -136,8 +136,8 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
       it 'raises RuntimeError' do
         expect(model).to receive(:transaction_open?).and_return(true)
 
-        expect { model.remove_concurrent_index(:users, :foo) }.
-          to raise_error(RuntimeError)
+        expect { model.remove_concurrent_index(:users, :foo) }
+          .to raise_error(RuntimeError)
       end
     end
   end
@@ -162,8 +162,8 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
         it 'creates a regular foreign key' do
           allow(Gitlab::Database).to receive(:mysql?).and_return(true)
 
-          expect(model).to receive(:add_foreign_key).
-            with(:projects, :users, column: :user_id, on_delete: :cascade)
+          expect(model).to receive(:add_foreign_key)
+            .with(:projects, :users, column: :user_id, on_delete: :cascade)
 
           model.add_concurrent_foreign_key(:projects, :users, column: :user_id)
         end
@@ -262,39 +262,53 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
   end
 
   describe '#update_column_in_batches' do
-    before do
-      create_list(:empty_project, 5)
-    end
+    context 'when running outside of a transaction' do
+      before do
+        expect(model).to receive(:transaction_open?).and_return(false)
 
-    it 'updates all the rows in a table' do
-      model.update_column_in_batches(:projects, :import_error, 'foo')
+        create_list(:empty_project, 5)
+      end
 
-      expect(Project.where(import_error: 'foo').count).to eq(5)
-    end
+      it 'updates all the rows in a table' do
+        model.update_column_in_batches(:projects, :import_error, 'foo')
+
+        expect(Project.where(import_error: 'foo').count).to eq(5)
+      end
 
-    it 'updates boolean values correctly' do
-      model.update_column_in_batches(:projects, :archived, true)
+      it 'updates boolean values correctly' do
+        model.update_column_in_batches(:projects, :archived, true)
 
-      expect(Project.where(archived: true).count).to eq(5)
-    end
+        expect(Project.where(archived: true).count).to eq(5)
+      end
 
-    context 'when a block is supplied' do
-      it 'yields an Arel table and query object to the supplied block' do
-        first_id = Project.first.id
+      context 'when a block is supplied' do
+        it 'yields an Arel table and query object to the supplied block' do
+          first_id = Project.first.id
 
-        model.update_column_in_batches(:projects, :archived, true) do |t, query|
-          query.where(t[:id].eq(first_id))
+          model.update_column_in_batches(:projects, :archived, true) do |t, query|
+            query.where(t[:id].eq(first_id))
+          end
+
+          expect(Project.where(archived: true).count).to eq(1)
         end
+      end
+
+      context 'when the value is Arel.sql (Arel::Nodes::SqlLiteral)' do
+        it 'updates the value as a SQL expression' do
+          model.update_column_in_batches(:projects, :star_count, Arel.sql('1+1'))
 
-        expect(Project.where(archived: true).count).to eq(1)
+          expect(Project.sum(:star_count)).to eq(2 * Project.count)
+        end
       end
     end
 
-    context 'when the value is Arel.sql (Arel::Nodes::SqlLiteral)' do
-      it 'updates the value as a SQL expression' do
-        model.update_column_in_batches(:projects, :star_count, Arel.sql('1+1'))
+    context 'when running inside the transaction' do
+      it 'raises RuntimeError' do
+        expect(model).to receive(:transaction_open?).and_return(true)
 
-        expect(Project.sum(:star_count)).to eq(2 * Project.count)
+        expect do
+          model.update_column_in_batches(:projects, :star_count, Arel.sql('1+1'))
+        end.to raise_error(RuntimeError)
       end
     end
   end
@@ -303,20 +317,22 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
     context 'outside of a transaction' do
       context 'when a column limit is not set' do
         before do
-          expect(model).to receive(:transaction_open?).and_return(false)
+          expect(model).to receive(:transaction_open?)
+            .and_return(false)
+            .at_least(:once)
 
           expect(model).to receive(:transaction).and_yield
 
-          expect(model).to receive(:add_column).
-            with(:projects, :foo, :integer, default: nil)
+          expect(model).to receive(:add_column)
+            .with(:projects, :foo, :integer, default: nil)
 
-          expect(model).to receive(:change_column_default).
-            with(:projects, :foo, 10)
+          expect(model).to receive(:change_column_default)
+            .with(:projects, :foo, 10)
         end
 
         it 'adds the column while allowing NULL values' do
-          expect(model).to receive(:update_column_in_batches).
-            with(:projects, :foo, 10)
+          expect(model).to receive(:update_column_in_batches)
+            .with(:projects, :foo, 10)
 
           expect(model).not_to receive(:change_column_null)
 
@@ -326,22 +342,22 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
         end
 
         it 'adds the column while not allowing NULL values' do
-          expect(model).to receive(:update_column_in_batches).
-            with(:projects, :foo, 10)
+          expect(model).to receive(:update_column_in_batches)
+            .with(:projects, :foo, 10)
 
-          expect(model).to receive(:change_column_null).
-            with(:projects, :foo, false)
+          expect(model).to receive(:change_column_null)
+            .with(:projects, :foo, false)
 
           model.add_column_with_default(:projects, :foo, :integer, default: 10)
         end
 
         it 'removes the added column whenever updating the rows fails' do
-          expect(model).to receive(:update_column_in_batches).
-            with(:projects, :foo, 10).
-            and_raise(RuntimeError)
+          expect(model).to receive(:update_column_in_batches)
+            .with(:projects, :foo, 10)
+            .and_raise(RuntimeError)
 
-          expect(model).to receive(:remove_column).
-            with(:projects, :foo)
+          expect(model).to receive(:remove_column)
+            .with(:projects, :foo)
 
           expect do
             model.add_column_with_default(:projects, :foo, :integer, default: 10)
@@ -349,12 +365,12 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
         end
 
         it 'removes the added column whenever changing a column NULL constraint fails' do
-          expect(model).to receive(:change_column_null).
-            with(:projects, :foo, false).
-            and_raise(RuntimeError)
+          expect(model).to receive(:change_column_null)
+            .with(:projects, :foo, false)
+            .and_raise(RuntimeError)
 
-          expect(model).to receive(:remove_column).
-            with(:projects, :foo)
+          expect(model).to receive(:remove_column)
+            .with(:projects, :foo)
 
           expect do
             model.add_column_with_default(:projects, :foo, :integer, default: 10)
@@ -370,8 +386,8 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
           allow(model).to receive(:change_column_null).with(:projects, :foo, false)
           allow(model).to receive(:change_column_default).with(:projects, :foo, 10)
 
-          expect(model).to receive(:add_column).
-            with(:projects, :foo, :integer, default: nil, limit: 8)
+          expect(model).to receive(:add_column)
+            .with(:projects, :foo, :integer, default: nil, limit: 8)
 
           model.add_column_with_default(:projects, :foo, :integer, default: 10, limit: 8)
         end
@@ -394,8 +410,8 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
       it 'raises RuntimeError' do
         allow(model).to receive(:transaction_open?).and_return(true)
 
-        expect { model.rename_column_concurrently(:users, :old, :new) }.
-          to raise_error(RuntimeError)
+        expect { model.rename_column_concurrently(:users, :old, :new) }
+          .to raise_error(RuntimeError)
       end
     end
 
@@ -426,17 +442,17 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
         it 'renames a column concurrently' do
           allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
 
-          expect(model).to receive(:install_rename_triggers_for_mysql).
-            with(trigger_name, 'users', 'old', 'new')
+          expect(model).to receive(:install_rename_triggers_for_mysql)
+            .with(trigger_name, 'users', 'old', 'new')
 
-          expect(model).to receive(:add_column).
-            with(:users, :new, :integer,
+          expect(model).to receive(:add_column)
+            .with(:users, :new, :integer,
                  limit: old_column.limit,
                  precision: old_column.precision,
                  scale: old_column.scale)
 
-          expect(model).to receive(:change_column_default).
-            with(:users, :new, old_column.default)
+          expect(model).to receive(:change_column_default)
+            .with(:users, :new, old_column.default)
 
           expect(model).to receive(:update_column_in_batches)
 
@@ -453,17 +469,17 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
         it 'renames a column concurrently' do
           allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
 
-          expect(model).to receive(:install_rename_triggers_for_postgresql).
-            with(trigger_name, 'users', 'old', 'new')
+          expect(model).to receive(:install_rename_triggers_for_postgresql)
+            .with(trigger_name, 'users', 'old', 'new')
 
-          expect(model).to receive(:add_column).
-            with(:users, :new, :integer,
+          expect(model).to receive(:add_column)
+            .with(:users, :new, :integer,
                  limit: old_column.limit,
                  precision: old_column.precision,
                  scale: old_column.scale)
 
-          expect(model).to receive(:change_column_default).
-            with(:users, :new, old_column.default)
+          expect(model).to receive(:change_column_default)
+            .with(:users, :new, old_column.default)
 
           expect(model).to receive(:update_column_in_batches)
 
@@ -482,8 +498,8 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
     it 'cleans up the renaming procedure for PostgreSQL' do
       allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
 
-      expect(model).to receive(:remove_rename_triggers_for_postgresql).
-        with(:users, /trigger_.{12}/)
+      expect(model).to receive(:remove_rename_triggers_for_postgresql)
+        .with(:users, /trigger_.{12}/)
 
       expect(model).to receive(:remove_column).with(:users, :old)
 
@@ -493,8 +509,8 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
     it 'cleans up the renaming procedure for MySQL' do
       allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
 
-      expect(model).to receive(:remove_rename_triggers_for_mysql).
-        with(/trigger_.{12}/)
+      expect(model).to receive(:remove_rename_triggers_for_mysql)
+        .with(/trigger_.{12}/)
 
       expect(model).to receive(:remove_column).with(:users, :old)
 
@@ -504,8 +520,8 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
 
   describe '#change_column_type_concurrently' do
     it 'changes the column type' do
-      expect(model).to receive(:rename_column_concurrently).
-        with('users', 'username', 'username_for_type_change', type: :text)
+      expect(model).to receive(:rename_column_concurrently)
+        .with('users', 'username', 'username_for_type_change', type: :text)
 
       model.change_column_type_concurrently('users', 'username', :text)
     end
@@ -513,11 +529,11 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
 
   describe '#cleanup_concurrent_column_type_change' do
     it 'cleans up the type changing procedure' do
-      expect(model).to receive(:cleanup_concurrent_column_rename).
-        with('users', 'username', 'username_for_type_change')
+      expect(model).to receive(:cleanup_concurrent_column_rename)
+        .with('users', 'username', 'username_for_type_change')
 
-      expect(model).to receive(:rename_column).
-        with('users', 'username_for_type_change', 'username')
+      expect(model).to receive(:rename_column)
+        .with('users', 'username_for_type_change', 'username')
 
       model.cleanup_concurrent_column_type_change('users', 'username')
     end
@@ -525,11 +541,11 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
 
   describe '#install_rename_triggers_for_postgresql' do
     it 'installs the triggers for PostgreSQL' do
-      expect(model).to receive(:execute).
-        with(/CREATE OR REPLACE FUNCTION foo()/m)
+      expect(model).to receive(:execute)
+        .with(/CREATE OR REPLACE FUNCTION foo()/m)
 
-      expect(model).to receive(:execute).
-        with(/CREATE TRIGGER foo/m)
+      expect(model).to receive(:execute)
+        .with(/CREATE TRIGGER foo/m)
 
       model.install_rename_triggers_for_postgresql('foo', :users, :old, :new)
     end
@@ -537,11 +553,11 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
 
   describe '#install_rename_triggers_for_mysql' do
     it 'installs the triggers for MySQL' do
-      expect(model).to receive(:execute).
-        with(/CREATE TRIGGER foo_insert.+ON users/m)
+      expect(model).to receive(:execute)
+        .with(/CREATE TRIGGER foo_insert.+ON users/m)
 
-      expect(model).to receive(:execute).
-        with(/CREATE TRIGGER foo_update.+ON users/m)
+      expect(model).to receive(:execute)
+        .with(/CREATE TRIGGER foo_update.+ON users/m)
 
       model.install_rename_triggers_for_mysql('foo', :users, :old, :new)
     end
@@ -567,8 +583,8 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
 
   describe '#rename_trigger_name' do
     it 'returns a String' do
-      expect(model.rename_trigger_name(:users, :foo, :bar)).
-        to match(/trigger_.{12}/)
+      expect(model.rename_trigger_name(:users, :foo, :bar))
+        .to match(/trigger_.{12}/)
     end
   end
 
@@ -607,11 +623,11 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
                        lengths: [],
                        orders: [])
 
-        allow(model).to receive(:indexes_for).with(:issues, 'project_id').
-          and_return([index])
+        allow(model).to receive(:indexes_for).with(:issues, 'project_id')
+          .and_return([index])
 
-        expect(model).to receive(:add_concurrent_index).
-          with(:issues,
+        expect(model).to receive(:add_concurrent_index)
+          .with(:issues,
                %w(gl_project_id),
                unique: false,
                name: 'index_on_issues_gl_project_id',
@@ -634,11 +650,11 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
                        lengths: [],
                        orders: [])
 
-        allow(model).to receive(:indexes_for).with(:issues, 'project_id').
-          and_return([index])
+        allow(model).to receive(:indexes_for).with(:issues, 'project_id')
+          .and_return([index])
 
-        expect(model).to receive(:add_concurrent_index).
-          with(:issues,
+        expect(model).to receive(:add_concurrent_index)
+          .with(:issues,
                %w(gl_project_id foobar),
                unique: false,
                name: 'index_on_issues_gl_project_id_foobar',
@@ -661,11 +677,11 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
                        lengths: [],
                        orders: [])
 
-        allow(model).to receive(:indexes_for).with(:issues, 'project_id').
-          and_return([index])
+        allow(model).to receive(:indexes_for).with(:issues, 'project_id')
+          .and_return([index])
 
-        expect(model).to receive(:add_concurrent_index).
-          with(:issues,
+        expect(model).to receive(:add_concurrent_index)
+          .with(:issues,
                %w(gl_project_id),
                unique: false,
                name: 'index_on_issues_gl_project_id',
@@ -689,11 +705,11 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
                        lengths: [],
                        orders: [])
 
-        allow(model).to receive(:indexes_for).with(:issues, 'project_id').
-          and_return([index])
+        allow(model).to receive(:indexes_for).with(:issues, 'project_id')
+          .and_return([index])
 
-        expect(model).to receive(:add_concurrent_index).
-          with(:issues,
+        expect(model).to receive(:add_concurrent_index)
+          .with(:issues,
                %w(gl_project_id),
                unique: false,
                name: 'index_on_issues_gl_project_id',
@@ -717,11 +733,11 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
                        lengths: [],
                        orders: [])
 
-        allow(model).to receive(:indexes_for).with(:issues, 'project_id').
-          and_return([index])
+        allow(model).to receive(:indexes_for).with(:issues, 'project_id')
+          .and_return([index])
 
-        expect(model).to receive(:add_concurrent_index).
-          with(:issues,
+        expect(model).to receive(:add_concurrent_index)
+          .with(:issues,
                %w(gl_project_id),
                unique: false,
                name: 'index_on_issues_gl_project_id',
@@ -745,11 +761,11 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
                        lengths: [],
                        orders: [])
 
-        allow(model).to receive(:indexes_for).with(:issues, 'project_id').
-          and_return([index])
+        allow(model).to receive(:indexes_for).with(:issues, 'project_id')
+          .and_return([index])
 
-        expect { model.copy_indexes(:issues, :project_id, :gl_project_id) }.
-          to raise_error(RuntimeError)
+        expect { model.copy_indexes(:issues, :project_id, :gl_project_id) }
+          .to raise_error(RuntimeError)
       end
     end
   end
@@ -761,11 +777,11 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
                   to_table: 'projects',
                   on_delete: :cascade)
 
-      allow(model).to receive(:foreign_keys_for).with(:issues, :project_id).
-        and_return([fk])
+      allow(model).to receive(:foreign_keys_for).with(:issues, :project_id)
+        .and_return([fk])
 
-      expect(model).to receive(:add_concurrent_foreign_key).
-        with('issues', 'projects', column: :gl_project_id, on_delete: :cascade)
+      expect(model).to receive(:add_concurrent_foreign_key)
+        .with('issues', 'projects', column: :gl_project_id, on_delete: :cascade)
 
       model.copy_foreign_keys(:issues, :project_id, :gl_project_id)
     end
@@ -790,8 +806,8 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
       end
 
       it 'builds the sql with correct functions' do
-        expect(model.replace_sql(Arel::Table.new(:users)[:first_name], "Alice", "Eve").to_s).
-          to include('regexp_replace')
+        expect(model.replace_sql(Arel::Table.new(:users)[:first_name], "Alice", "Eve").to_s)
+          .to include('regexp_replace')
       end
     end
 
@@ -801,8 +817,8 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
       end
 
       it 'builds the sql with the correct functions' do
-        expect(model.replace_sql(Arel::Table.new(:users)[:first_name], "Alice", "Eve").to_s).
-          to include('locate', 'insert')
+        expect(model.replace_sql(Arel::Table.new(:users)[:first_name], "Alice", "Eve").to_s)
+          .to include('locate', 'insert')
       end
     end
 
@@ -810,7 +826,11 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
       let!(:user) { create(:user, name: 'Kathy Alice Aliceson') }
 
       it 'replaces the correct part of the string' do
-        model.update_column_in_batches(:users, :name, model.replace_sql(Arel::Table.new(:users)[:name], 'Alice', 'Eve'))
+        allow(model).to receive(:transaction_open?).and_return(false)
+        query = model.replace_sql(Arel::Table.new(:users)[:name], 'Alice', 'Eve')
+
+        model.update_column_in_batches(:users, :name, query)
+
         expect(user.reload.name).to eq('Kathy Eve Aliceson')
       end
     end
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
index a3ab4e3dd9e2c45cf9783faf104d2a7fc2ba59ac..8813f129ef5d0bd329403b8d7fc4951cf04a4207 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
@@ -1,11 +1,12 @@
 require 'spec_helper'
 
-describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase do
+describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :truncate do
   let(:migration) { FakeRenameReservedPathMigrationV1.new }
   let(:subject) { described_class.new(['the-path'], migration) }
 
   before do
     allow(migration).to receive(:say)
+    TestEnv.clean_test_path
   end
 
   def migration_namespace(namespace)
@@ -153,6 +154,30 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase do
     end
   end
 
+  describe '#perform_rename' do
+    describe 'for namespaces' do
+      let(:namespace) { create(:namespace, path: 'the-path') }
+      it 'renames the path' do
+        subject.perform_rename(migration_namespace(namespace), 'the-path', 'renamed')
+
+        expect(namespace.reload.path).to eq('renamed')
+      end
+
+      it 'renames all the routes for the namespace' do
+        child = create(:group, path: 'child', parent: namespace)
+        project = create(:project, namespace: child, path: 'the-project')
+        other_one = create(:namespace, path: 'the-path-is-similar')
+
+        subject.perform_rename(migration_namespace(namespace), 'the-path', 'renamed')
+
+        expect(namespace.reload.route.path).to eq('renamed')
+        expect(child.reload.route.path).to eq('renamed/child')
+        expect(project.reload.route.path).to eq('renamed/child/the-project')
+        expect(other_one.reload.route.path).to eq('the-path-is-similar')
+      end
+    end
+  end
+
   describe '#move_pages' do
     it 'moves the pages directory' do
       expect(subject).to receive(:move_folders)
@@ -203,4 +228,53 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase do
       expect(File.exist?(expected_file)).to be(true)
     end
   end
+
+  describe '#track_rename', redis: true do
+    it 'tracks a rename in redis' do
+      key = 'rename:FakeRenameReservedPathMigrationV1:namespace'
+
+      subject.track_rename('namespace', 'path/to/namespace', 'path/to/renamed')
+
+      old_path, new_path = [nil, nil]
+      Gitlab::Redis.with do |redis|
+        rename_info = redis.lpop(key)
+        old_path, new_path = JSON.parse(rename_info)
+      end
+
+      expect(old_path).to eq('path/to/namespace')
+      expect(new_path).to eq('path/to/renamed')
+    end
+  end
+
+  describe '#reverts_for_type', redis: true do
+    it 'yields for each tracked rename' do
+      subject.track_rename('project', 'old_path', 'new_path')
+      subject.track_rename('project', 'old_path2', 'new_path2')
+      subject.track_rename('namespace', 'namespace_path', 'new_namespace_path')
+
+      expect { |b| subject.reverts_for_type('project', &b) }
+        .to yield_successive_args(%w(old_path2 new_path2), %w(old_path new_path))
+      expect { |b| subject.reverts_for_type('namespace', &b) }
+        .to yield_with_args('namespace_path', 'new_namespace_path')
+    end
+
+    it 'keeps the revert in redis if it failed' do
+      subject.track_rename('project', 'old_path', 'new_path')
+
+      subject.reverts_for_type('project') do
+        raise 'whatever happens, keep going!'
+      end
+
+      key = 'rename:FakeRenameReservedPathMigrationV1:project'
+      stored_renames = nil
+      rename_count = 0
+      Gitlab::Redis.with do |redis|
+        stored_renames = redis.lrange(key, 0, 1)
+        rename_count = redis.llen(key)
+      end
+
+      expect(rename_count).to eq(1)
+      expect(JSON.parse(stored_renames.first)).to eq(%w(old_path new_path))
+    end
+  end
 end
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
index ce2b5d620fdcf464089df5235cb2c03d97e5c25c..803e923b4a58e7e756b5124d6d1b21c25f54e3f8 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
@@ -1,11 +1,13 @@
 require 'spec_helper'
 
-describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
+describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, :truncate do
   let(:migration) { FakeRenameReservedPathMigrationV1.new }
   let(:subject) { described_class.new(['the-path'], migration) }
+  let(:namespace) { create(:group, name: 'the-path') }
 
   before do
     allow(migration).to receive(:say)
+    TestEnv.clean_test_path
   end
 
   def migration_namespace(namespace)
@@ -21,8 +23,8 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
         parent = create(:group, path: 'parent')
         child = create(:group, path: 'the-path', parent: parent)
 
-        found_ids = subject.namespaces_for_paths(type: :child).
-                      map(&:id)
+        found_ids = subject.namespaces_for_paths(type: :child)
+                      .map(&:id)
 
         expect(found_ids).to contain_exactly(child.id)
       end
@@ -38,8 +40,8 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
                            path: 'the-path',
                            parent: create(:group))
 
-        found_ids = subject.namespaces_for_paths(type: :child).
-                      map(&:id)
+        found_ids = subject.namespaces_for_paths(type: :child)
+                      .map(&:id)
 
         expect(found_ids).to contain_exactly(namespace.id)
       end
@@ -53,8 +55,8 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
                            path: 'the-path',
                            parent: create(:group))
 
-        found_ids = subject.namespaces_for_paths(type: :child).
-                      map(&:id)
+        found_ids = subject.namespaces_for_paths(type: :child)
+                      .map(&:id)
 
         expect(found_ids).to contain_exactly(namespace.id)
       end
@@ -68,8 +70,8 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
                                   path: 'the-path',
                                   parent: create(:group))
 
-        found_ids = subject.namespaces_for_paths(type: :top_level).
-                      map(&:id)
+        found_ids = subject.namespaces_for_paths(type: :top_level)
+                      .map(&:id)
 
         expect(found_ids).to contain_exactly(root_namespace.id)
       end
@@ -81,8 +83,8 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
                                   path: 'the-path',
                                   parent: create(:group))
 
-        found_ids = subject.namespaces_for_paths(type: :top_level).
-                      map(&:id)
+        found_ids = subject.namespaces_for_paths(type: :top_level)
+                      .map(&:id)
 
         expect(found_ids).to contain_exactly(root_namespace.id)
       end
@@ -137,23 +139,37 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
   end
 
   describe "#rename_namespace" do
-    let(:namespace) { create(:group, name: 'the-path') }
-
     it 'renames paths & routes for the namespace' do
-      expect(subject).to receive(:rename_path_for_routable).
-                           with(namespace).
-                           and_call_original
+      expect(subject).to receive(:rename_path_for_routable)
+                           .with(namespace)
+                           .and_call_original
 
       subject.rename_namespace(namespace)
 
       expect(namespace.reload.path).to eq('the-path0')
     end
 
+    it 'tracks the rename' do
+      expect(subject).to receive(:track_rename)
+                           .with('namespace', 'the-path', 'the-path0')
+
+      subject.rename_namespace(namespace)
+    end
+
+    it 'renames things related to the namespace' do
+      expect(subject).to receive(:rename_namespace_dependencies)
+                           .with(namespace, 'the-path', 'the-path0')
+
+      subject.rename_namespace(namespace)
+    end
+  end
+
+  describe '#rename_namespace_dependencies' do
     it "moves the the repository for a project in the namespace" do
       create(:project, namespace: namespace, path: "the-path-project")
       expected_repo = File.join(TestEnv.repos_path, "the-path0", "the-path-project.git")
 
-      subject.rename_namespace(namespace)
+      subject.rename_namespace_dependencies(namespace, 'the-path', 'the-path0')
 
       expect(File.directory?(expected_repo)).to be(true)
     end
@@ -161,13 +177,13 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
     it "moves the uploads for the namespace" do
       expect(subject).to receive(:move_uploads).with("the-path", "the-path0")
 
-      subject.rename_namespace(namespace)
+      subject.rename_namespace_dependencies(namespace, 'the-path', 'the-path0')
     end
 
     it "moves the pages for the namespace" do
       expect(subject).to receive(:move_pages).with("the-path", "the-path0")
 
-      subject.rename_namespace(namespace)
+      subject.rename_namespace_dependencies(namespace, 'the-path', 'the-path0')
     end
 
     it 'invalidates the markdown cache of related projects' do
@@ -175,13 +191,13 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
 
       expect(subject).to receive(:remove_cached_html_for_projects).with([project.id])
 
-      subject.rename_namespace(namespace)
+      subject.rename_namespace_dependencies(namespace, 'the-path', 'the-path0')
     end
 
     it "doesn't rename users for other namespaces" do
       expect(subject).not_to receive(:rename_user)
 
-      subject.rename_namespace(namespace)
+      subject.rename_namespace_dependencies(namespace, 'the-path', 'the-path0')
     end
 
     it 'renames the username of a namespace for a user' do
@@ -189,7 +205,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
 
       expect(subject).to receive(:rename_user).with('the-path', 'the-path0')
 
-      subject.rename_namespace(user.namespace)
+      subject.rename_namespace_dependencies(user.namespace, 'the-path', 'the-path0')
     end
   end
 
@@ -211,17 +227,63 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
     end
 
     it 'renames top level namespaces the namespace' do
-      expect(subject).to receive(:rename_namespace).
-                           with(migration_namespace(top_level_namespace))
+      expect(subject).to receive(:rename_namespace)
+                           .with(migration_namespace(top_level_namespace))
 
       subject.rename_namespaces(type: :top_level)
     end
 
     it 'renames child namespaces' do
-      expect(subject).to receive(:rename_namespace).
-                           with(migration_namespace(child_namespace))
+      expect(subject).to receive(:rename_namespace)
+                           .with(migration_namespace(child_namespace))
 
       subject.rename_namespaces(type: :child)
     end
   end
+
+  describe '#revert_renames', redis: true do
+    it 'renames the routes back to the previous values' do
+      project = create(:project, path: 'a-project', namespace: namespace)
+      subject.rename_namespace(namespace)
+
+      expect(subject).to receive(:perform_rename)
+                           .with(
+                             kind_of(Gitlab::Database::RenameReservedPathsMigration::V1::MigrationClasses::Namespace),
+                             'the-path0',
+                             'the-path'
+                           ).and_call_original
+
+      subject.revert_renames
+
+      expect(namespace.reload.path).to eq('the-path')
+      expect(namespace.reload.route.path).to eq('the-path')
+      expect(project.reload.route.path).to eq('the-path/a-project')
+    end
+
+    it 'moves the repositories back to their original place' do
+      project = create(:project, path: 'a-project', namespace: namespace)
+      project.create_repository
+      subject.rename_namespace(namespace)
+
+      expected_path = File.join(TestEnv.repos_path, 'the-path', 'a-project.git')
+
+      expect(subject).to receive(:rename_namespace_dependencies)
+                           .with(
+                             kind_of(Gitlab::Database::RenameReservedPathsMigration::V1::MigrationClasses::Namespace),
+                             'the-path0',
+                             'the-path'
+                           ).and_call_original
+
+      subject.revert_renames
+
+      expect(File.directory?(expected_path)).to be_truthy
+    end
+
+    it "doesn't break when the namespace was renamed" do
+      subject.rename_namespace(namespace)
+      namespace.update_attributes!(path: 'renamed-afterwards')
+
+      expect { subject.revert_renames }.not_to raise_error
+    end
+  end
 end
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb
index 59e8de2712d05b0a7a3418501985a3460081b5b5..0e240a5ccf161a654c75dc0dfcbcfef632a130d8 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb
@@ -1,11 +1,17 @@
 require 'spec_helper'
 
-describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects do
+describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :truncate do
   let(:migration) { FakeRenameReservedPathMigrationV1.new }
   let(:subject) { described_class.new(['the-path'], migration) }
+  let(:project) do
+    create(:empty_project,
+           path: 'the-path',
+           namespace: create(:namespace, path: 'known-parent' ))
+  end
 
   before do
     allow(migration).to receive(:say)
+    TestEnv.clean_test_path
   end
 
   describe '#projects_for_paths' do
@@ -13,8 +19,8 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects do
       namespace = create(:namespace, path: 'hello')
       project = create(:empty_project, path: 'THE-path', namespace: namespace)
 
-      result_ids = described_class.new(['Hello/the-path'], migration).
-                     projects_for_paths.map(&:id)
+      result_ids = described_class.new(['Hello/the-path'], migration)
+                     .projects_for_paths.map(&:id)
 
       expect(result_ids).to contain_exactly(project.id)
     end
@@ -39,51 +45,60 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects do
     end
 
     it 'invalidates the markdown cache of related projects' do
-      expect(subject).to receive(:remove_cached_html_for_projects).
-                           with(projects.map(&:id))
+      expect(subject).to receive(:remove_cached_html_for_projects)
+                           .with(projects.map(&:id))
 
       subject.rename_projects
     end
   end
 
   describe '#rename_project' do
-    let(:project) do
-      create(:empty_project,
-             path: 'the-path',
-             namespace: create(:namespace, path: 'known-parent' ))
-    end
-
     it 'renames path & route for the project' do
-      expect(subject).to receive(:rename_path_for_routable).
-                           with(project).
-                           and_call_original
+      expect(subject).to receive(:rename_path_for_routable)
+                           .with(project)
+                           .and_call_original
 
       subject.rename_project(project)
 
       expect(project.reload.path).to eq('the-path0')
     end
 
-    it 'moves the wiki & the repo' do
-      expect(subject).to receive(:move_repository).
-                           with(project, 'known-parent/the-path.wiki', 'known-parent/the-path0.wiki')
-      expect(subject).to receive(:move_repository).
-                           with(project, 'known-parent/the-path', 'known-parent/the-path0')
+    it 'tracks the rename' do
+      expect(subject).to receive(:track_rename)
+                           .with('project', 'known-parent/the-path', 'known-parent/the-path0')
 
       subject.rename_project(project)
     end
 
-    it 'moves uploads' do
-      expect(subject).to receive(:move_uploads).
-                           with('known-parent/the-path', 'known-parent/the-path0')
+    it 'renames the folders for the project' do
+      expect(subject).to receive(:move_project_folders).with(project, 'known-parent/the-path', 'known-parent/the-path0')
 
       subject.rename_project(project)
     end
+  end
+
+  describe '#move_project_folders' do
+    it 'moves the wiki & the repo' do
+      expect(subject).to receive(:move_repository)
+                           .with(project, 'known-parent/the-path.wiki', 'known-parent/the-path0.wiki')
+      expect(subject).to receive(:move_repository)
+                           .with(project, 'known-parent/the-path', 'known-parent/the-path0')
+
+      subject.move_project_folders(project, 'known-parent/the-path', 'known-parent/the-path0')
+    end
+
+    it 'moves uploads' do
+      expect(subject).to receive(:move_uploads)
+                           .with('known-parent/the-path', 'known-parent/the-path0')
+
+      subject.move_project_folders(project, 'known-parent/the-path', 'known-parent/the-path0')
+    end
 
     it 'moves pages' do
-      expect(subject).to receive(:move_pages).
-                           with('known-parent/the-path', 'known-parent/the-path0')
+      expect(subject).to receive(:move_pages)
+                           .with('known-parent/the-path', 'known-parent/the-path0')
 
-      subject.rename_project(project)
+      subject.move_project_folders(project, 'known-parent/the-path', 'known-parent/the-path0')
     end
   end
 
@@ -99,4 +114,47 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects do
       expect(File.directory?(expected_path)).to be(true)
     end
   end
+
+  describe '#revert_renames', redis: true do
+    it 'renames the routes back to the previous values' do
+      subject.rename_project(project)
+
+      expect(subject).to receive(:perform_rename)
+                           .with(
+                             kind_of(Gitlab::Database::RenameReservedPathsMigration::V1::MigrationClasses::Project),
+                             'known-parent/the-path0',
+                             'known-parent/the-path'
+                           ).and_call_original
+
+      subject.revert_renames
+
+      expect(project.reload.path).to eq('the-path')
+      expect(project.route.path).to eq('known-parent/the-path')
+    end
+
+    it 'moves the repositories back to their original place' do
+      project.create_repository
+      subject.rename_project(project)
+
+      expected_path = File.join(TestEnv.repos_path, 'known-parent', 'the-path.git')
+
+      expect(subject).to receive(:move_project_folders)
+                           .with(
+                             kind_of(Gitlab::Database::RenameReservedPathsMigration::V1::MigrationClasses::Project),
+                             'known-parent/the-path0',
+                             'known-parent/the-path'
+                           ).and_call_original
+
+      subject.revert_renames
+
+      expect(File.directory?(expected_path)).to be_truthy
+    end
+
+    it "doesn't break when the project was renamed" do
+      subject.rename_project(project)
+      project.update_attributes!(path: 'renamed-afterwards')
+
+      expect { subject.revert_renames }.not_to raise_error
+    end
+  end
 end
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb
index f8cc1eb91ec9945097e9f1e77ba713ea99a8c495..7695b95dc576d14db506bcf1f967a483e7703637 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb
@@ -3,17 +3,17 @@ require 'spec_helper'
 shared_examples 'renames child namespaces' do |type|
   it 'renames namespaces' do
     rename_namespaces = double
-    expect(described_class::RenameNamespaces).
-      to receive(:new).with(['first-path', 'second-path'], subject).
-           and_return(rename_namespaces)
-    expect(rename_namespaces).to receive(:rename_namespaces).
-                                   with(type: :child)
+    expect(described_class::RenameNamespaces)
+      .to receive(:new).with(['first-path', 'second-path'], subject)
+           .and_return(rename_namespaces)
+    expect(rename_namespaces).to receive(:rename_namespaces)
+                                   .with(type: :child)
 
     subject.rename_wildcard_paths(['first-path', 'second-path'])
   end
 end
 
-describe Gitlab::Database::RenameReservedPathsMigration::V1 do
+describe Gitlab::Database::RenameReservedPathsMigration::V1, :truncate do
   let(:subject) { FakeRenameReservedPathMigrationV1.new }
 
   before do
@@ -29,9 +29,9 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1 do
 
     it 'should rename projects' do
       rename_projects = double
-      expect(described_class::RenameProjects).
-        to receive(:new).with(['the-path'], subject).
-             and_return(rename_projects)
+      expect(described_class::RenameProjects)
+        .to receive(:new).with(['the-path'], subject)
+             .and_return(rename_projects)
 
       expect(rename_projects).to receive(:rename_projects)
 
@@ -42,13 +42,35 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1 do
   describe '#rename_root_paths' do
     it 'should rename namespaces' do
       rename_namespaces = double
-      expect(described_class::RenameNamespaces).
-        to receive(:new).with(['the-path'], subject).
-             and_return(rename_namespaces)
-      expect(rename_namespaces).to receive(:rename_namespaces).
-                           with(type: :top_level)
+      expect(described_class::RenameNamespaces)
+        .to receive(:new).with(['the-path'], subject)
+             .and_return(rename_namespaces)
+      expect(rename_namespaces).to receive(:rename_namespaces)
+                           .with(type: :top_level)
 
       subject.rename_root_paths('the-path')
     end
   end
+
+  describe '#revert_renames' do
+    it 'renames namespaces' do
+      rename_namespaces = double
+      expect(described_class::RenameNamespaces)
+        .to receive(:new).with([], subject)
+              .and_return(rename_namespaces)
+      expect(rename_namespaces).to receive(:revert_renames)
+
+      subject.revert_renames
+    end
+
+    it 'renames projects' do
+      rename_projects = double
+      expect(described_class::RenameProjects)
+        .to receive(:new).with([], subject)
+              .and_return(rename_projects)
+      expect(rename_projects).to receive(:revert_renames)
+
+      subject.revert_renames
+    end
+  end
 end
diff --git a/spec/lib/gitlab/database/sha_attribute_spec.rb b/spec/lib/gitlab/database/sha_attribute_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..62c1d37ea1c8d422e2065343367e4f330d99147a
--- /dev/null
+++ b/spec/lib/gitlab/database/sha_attribute_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe Gitlab::Database::ShaAttribute do
+  let(:sha) do
+    '9a573a369a5bfbb9a4a36e98852c21af8a44ea8b'
+  end
+
+  let(:binary_sha) do
+    [sha].pack('H*')
+  end
+
+  let(:binary_from_db) do
+    if Gitlab::Database.postgresql?
+      "\\x#{sha}"
+    else
+      binary_sha
+    end
+  end
+
+  let(:attribute) { described_class.new }
+
+  describe '#type_cast_from_database' do
+    it 'converts the binary SHA to a String' do
+      expect(attribute.type_cast_from_database(binary_from_db)).to eq(sha)
+    end
+  end
+
+  describe '#type_cast_for_database' do
+    it 'converts a SHA String to binary data' do
+      expect(attribute.type_cast_for_database(sha).to_s).to eq(binary_sha)
+    end
+  end
+end
diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb
index 26e5d73d3334f16533290e4a6c6646379eb07ee8..cbf6c35356e8cffd55f173ebc5d70a96d233291e 100644
--- a/spec/lib/gitlab/database_spec.rb
+++ b/spec/lib/gitlab/database_spec.rb
@@ -34,8 +34,8 @@ describe Gitlab::Database, lib: true do
   describe '.version' do
     context "on mysql" do
       it "extracts the version number" do
-        allow(described_class).to receive(:database_version).
-          and_return("5.7.12-standard")
+        allow(described_class).to receive(:database_version)
+          .and_return("5.7.12-standard")
 
         expect(described_class.version).to eq '5.7.12-standard'
       end
@@ -43,8 +43,8 @@ describe Gitlab::Database, lib: true do
 
     context "on postgresql" do
       it "extracts the version number" do
-        allow(described_class).to receive(:database_version).
-          and_return("PostgreSQL 9.4.4 on x86_64-apple-darwin14.3.0")
+        allow(described_class).to receive(:database_version)
+          .and_return("PostgreSQL 9.4.4 on x86_64-apple-darwin14.3.0")
 
         expect(described_class.version).to eq '9.4.4'
       end
@@ -129,6 +129,59 @@ describe Gitlab::Database, lib: true do
     end
   end
 
+  describe '.bulk_insert' do
+    before do
+      allow(described_class).to receive(:connection).and_return(connection)
+      allow(connection).to receive(:quote_column_name, &:itself)
+      allow(connection).to receive(:quote, &:itself)
+      allow(connection).to receive(:execute)
+    end
+
+    let(:connection) { double(:connection) }
+
+    let(:rows) do
+      [
+        { a: 1, b: 2, c: 3 },
+        { c: 6, a: 4, b: 5 }
+      ]
+    end
+
+    it 'does nothing with empty rows' do
+      expect(connection).not_to receive(:execute)
+
+      described_class.bulk_insert('test', [])
+    end
+
+    it 'uses the ordering from the first row' do
+      expect(connection).to receive(:execute) do |sql|
+        expect(sql).to include('(1, 2, 3)')
+        expect(sql).to include('(4, 5, 6)')
+      end
+
+      described_class.bulk_insert('test', rows)
+    end
+
+    it 'quotes column names' do
+      expect(connection).to receive(:quote_column_name).with(:a)
+      expect(connection).to receive(:quote_column_name).with(:b)
+      expect(connection).to receive(:quote_column_name).with(:c)
+
+      described_class.bulk_insert('test', rows)
+    end
+
+    it 'quotes values' do
+      1.upto(6) do |i|
+        expect(connection).to receive(:quote).with(i)
+      end
+
+      described_class.bulk_insert('test', rows)
+    end
+
+    it 'handles non-UTF-8 data' do
+      expect { described_class.bulk_insert('test', [{ a: "\255" }]) }.not_to raise_error
+    end
+  end
+
   describe '.create_connection_pool' do
     it 'creates a new connection pool with specific pool size' do
       pool = described_class.create_connection_pool(5)
diff --git a/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb b/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb
index 4da8821726cd2a16a172a5a2a976e6f03c2a8e24..64b233f3e685a4184cb3e2b6d2375085440aabae 100644
--- a/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb
@@ -54,6 +54,8 @@ describe Gitlab::DependencyLinker::RequirementsTxtLinker, lib: true do
         Sphinx>=1.3
         docutils>=0.7
         markupsafe
+        pytest~=3.0
+        foop!=3.0
       CONTENT
     end
 
@@ -78,10 +80,16 @@ describe Gitlab::DependencyLinker::RequirementsTxtLinker, lib: true do
       expect(subject).to include(link('Sphinx', 'https://pypi.python.org/pypi/Sphinx'))
       expect(subject).to include(link('docutils', 'https://pypi.python.org/pypi/docutils'))
       expect(subject).to include(link('markupsafe', 'https://pypi.python.org/pypi/markupsafe'))
+      expect(subject).to include(link('pytest', 'https://pypi.python.org/pypi/pytest'))
+      expect(subject).to include(link('foop', 'https://pypi.python.org/pypi/foop'))
     end
 
     it 'links URLs' do
       expect(subject).to include(link('http://wxpython.org/Phoenix/snapshot-builds/wxPython_Phoenix-3.0.3.dev1820+49a8884-cp34-none-win_amd64.whl', 'http://wxpython.org/Phoenix/snapshot-builds/wxPython_Phoenix-3.0.3.dev1820+49a8884-cp34-none-win_amd64.whl'))
     end
+
+    it 'does not contain link with a newline as package name' do
+      expect(subject).not_to include(link("\n", "https://pypi.python.org/pypi/\n"))
+    end
   end
 end
diff --git a/spec/lib/gitlab/downtime_check_spec.rb b/spec/lib/gitlab/downtime_check_spec.rb
index 42d895e548e3aa97b948034d83a8148f46c77f4e..1f1e4e0216c4267dc9ae34aca4929102e62ff3aa 100644
--- a/spec/lib/gitlab/downtime_check_spec.rb
+++ b/spec/lib/gitlab/downtime_check_spec.rb
@@ -11,12 +11,12 @@ describe Gitlab::DowntimeCheck do
 
     context 'when a migration does not specify if downtime is required' do
       it 'raises RuntimeError' do
-        expect(subject).to receive(:class_for_migration_file).
-          with(path).
-          and_return(Class.new)
+        expect(subject).to receive(:class_for_migration_file)
+          .with(path)
+          .and_return(Class.new)
 
-        expect { subject.check([path]) }.
-          to raise_error(RuntimeError, /it requires downtime/)
+        expect { subject.check([path]) }
+          .to raise_error(RuntimeError, /it requires downtime/)
       end
     end
 
@@ -25,12 +25,12 @@ describe Gitlab::DowntimeCheck do
         it 'raises RuntimeError' do
           stub_const('TestMigration::DOWNTIME', true)
 
-          expect(subject).to receive(:class_for_migration_file).
-            with(path).
-            and_return(TestMigration)
+          expect(subject).to receive(:class_for_migration_file)
+            .with(path)
+            .and_return(TestMigration)
 
-          expect { subject.check([path]) }.
-            to raise_error(RuntimeError, /no reason was given/)
+          expect { subject.check([path]) }
+            .to raise_error(RuntimeError, /no reason was given/)
         end
       end
 
@@ -39,9 +39,9 @@ describe Gitlab::DowntimeCheck do
           stub_const('TestMigration::DOWNTIME', true)
           stub_const('TestMigration::DOWNTIME_REASON', 'foo')
 
-          expect(subject).to receive(:class_for_migration_file).
-            with(path).
-            and_return(TestMigration)
+          expect(subject).to receive(:class_for_migration_file)
+            .with(path)
+            .and_return(TestMigration)
 
           messages = subject.check([path])
 
@@ -65,9 +65,9 @@ describe Gitlab::DowntimeCheck do
 
       expect(subject).to receive(:require).with(path)
 
-      expect(subject).to receive(:class_for_migration_file).
-        with(path).
-        and_return(TestMigration)
+      expect(subject).to receive(:class_for_migration_file)
+        .with(path)
+        .and_return(TestMigration)
 
       expect(subject).to receive(:puts).with(an_instance_of(String))
 
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 3f79eaf7afbb308a84941fdd2b83980a2dc646e0..cd0309e248de1fcdf0d9c8ae1374cdf5e2a7c1bc 100644
--- a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
@@ -91,7 +91,7 @@ describe Gitlab::Email::Handler::CreateNoteHandler, lib: true do
     end
   end
 
-  context 'when the note contains slash commands' do
+  context 'when the note contains quick actions' do
     let!(:email_raw) { fixture_file("emails/commands_in_reply.eml") }
 
     context 'and current user cannot update noteable' do
diff --git a/spec/lib/gitlab/email/reply_parser_spec.rb b/spec/lib/gitlab/email/reply_parser_spec.rb
index 28698e89c338cee596d73e3cb9ddebc370c9b3cd..2ea5e6460a35d1377777c27b803bc492d60d678a 100644
--- a/spec/lib/gitlab/email/reply_parser_spec.rb
+++ b/spec/lib/gitlab/email/reply_parser_spec.rb
@@ -20,8 +20,8 @@ describe Gitlab::Email::ReplyParser, lib: true do
     end
 
     it "properly renders plaintext-only email" do
-      expect(test_parse_body(fixture_file("emails/plaintext_only.eml"))).
-        to eq(
+      expect(test_parse_body(fixture_file("emails/plaintext_only.eml")))
+        .to eq(
           <<-BODY.strip_heredoc.chomp
             ### reply from default mail client in Windows 8.1 Metro
 
@@ -46,8 +46,8 @@ describe Gitlab::Email::ReplyParser, lib: true do
     end
 
     it "handles multiple paragraphs" do
-      expect(test_parse_body(fixture_file("emails/paragraphs.eml"))).
-        to eq(
+      expect(test_parse_body(fixture_file("emails/paragraphs.eml")))
+        .to eq(
           <<-BODY.strip_heredoc.chomp
             Is there any reason the *old* candy can't be be kept in silos while the new candy
             is imported into *new* silos?
@@ -61,8 +61,8 @@ describe Gitlab::Email::ReplyParser, lib: true do
     end
 
     it "handles multiple paragraphs when parsing html" do
-      expect(test_parse_body(fixture_file("emails/html_paragraphs.eml"))).
-        to eq(
+      expect(test_parse_body(fixture_file("emails/html_paragraphs.eml")))
+        .to eq(
           <<-BODY.strip_heredoc.chomp
             Awesome!
 
@@ -74,8 +74,8 @@ describe Gitlab::Email::ReplyParser, lib: true do
     end
 
     it "handles newlines" do
-      expect(test_parse_body(fixture_file("emails/newlines.eml"))).
-        to eq(
+      expect(test_parse_body(fixture_file("emails/newlines.eml")))
+        .to eq(
           <<-BODY.strip_heredoc.chomp
             This is my reply.
             It is my best reply.
@@ -85,8 +85,8 @@ describe Gitlab::Email::ReplyParser, lib: true do
     end
 
     it "handles inline reply" do
-      expect(test_parse_body(fixture_file("emails/inline_reply.eml"))).
-        to eq(
+      expect(test_parse_body(fixture_file("emails/inline_reply.eml")))
+        .to eq(
           <<-BODY.strip_heredoc.chomp
             >     techAPJ <https://meta.discourse.org/users/techapj>
             > November 28
@@ -132,8 +132,8 @@ describe Gitlab::Email::ReplyParser, lib: true do
     end
 
     it "properly renders email reply from gmail web client" do
-      expect(test_parse_body(fixture_file("emails/gmail_web.eml"))).
-        to eq(
+      expect(test_parse_body(fixture_file("emails/gmail_web.eml")))
+        .to eq(
           <<-BODY.strip_heredoc.chomp
             ### This is a reply from standard GMail in Google Chrome.
 
@@ -151,8 +151,8 @@ describe Gitlab::Email::ReplyParser, lib: true do
     end
 
     it "properly renders email reply from iOS default mail client" do
-      expect(test_parse_body(fixture_file("emails/ios_default.eml"))).
-        to eq(
+      expect(test_parse_body(fixture_file("emails/ios_default.eml")))
+        .to eq(
           <<-BODY.strip_heredoc.chomp
             ### this is a reply from iOS default mail
 
@@ -166,8 +166,8 @@ describe Gitlab::Email::ReplyParser, lib: true do
     end
 
     it "properly renders email reply from Android 5 gmail client" do
-      expect(test_parse_body(fixture_file("emails/android_gmail.eml"))).
-        to eq(
+      expect(test_parse_body(fixture_file("emails/android_gmail.eml")))
+        .to eq(
           <<-BODY.strip_heredoc.chomp
             ### this is a reply from Android 5 gmail
 
@@ -184,8 +184,8 @@ describe Gitlab::Email::ReplyParser, lib: true do
     end
 
     it "properly renders email reply from Windows 8.1 Metro default mail client" do
-      expect(test_parse_body(fixture_file("emails/windows_8_metro.eml"))).
-        to eq(
+      expect(test_parse_body(fixture_file("emails/windows_8_metro.eml")))
+        .to eq(
           <<-BODY.strip_heredoc.chomp
             ### reply from default mail client in Windows 8.1 Metro
 
@@ -208,5 +208,9 @@ describe Gitlab::Email::ReplyParser, lib: true do
     it "properly renders html-only email from MS Outlook" do
       expect(test_parse_body(fixture_file("emails/outlook_html.eml"))).to eq("Microsoft Outlook 2010")
     end
+
+    it "does not wrap links with no href in unnecessary brackets" do
+      expect(test_parse_body(fixture_file("emails/html_empty_link.eml"))).to eq("no brackets!")
+    end
   end
 end
diff --git a/spec/lib/gitlab/etag_caching/middleware_spec.rb b/spec/lib/gitlab/etag_caching/middleware_spec.rb
index 4acf4f047f15a477fcef4a336191257037a48872..4a54d641b4ea8c55c2f7136fbd204e24ba5cc8ef 100644
--- a/spec/lib/gitlab/etag_caching/middleware_spec.rb
+++ b/spec/lib/gitlab/etag_caching/middleware_spec.rb
@@ -108,8 +108,8 @@ describe Gitlab::EtagCaching::Middleware do
 
     context 'when polling is disabled' do
       before do
-        allow(Gitlab::PollingInterval).to receive(:polling_enabled?).
-          and_return(false)
+        allow(Gitlab::PollingInterval).to receive(:polling_enabled?)
+          .and_return(false)
       end
 
       it 'returns status code 429' do
diff --git a/spec/lib/gitlab/exclusive_lease_spec.rb b/spec/lib/gitlab/exclusive_lease_spec.rb
index a366d68a1460772624a89d04455f43ce6605b13b..81bbd70ffb8c78574b1d9f7105dbf57ea1601aeb 100644
--- a/spec/lib/gitlab/exclusive_lease_spec.rb
+++ b/spec/lib/gitlab/exclusive_lease_spec.rb
@@ -19,6 +19,19 @@ describe Gitlab::ExclusiveLease, type: :redis do
     end
   end
 
+  describe '#renew' do
+    it 'returns true when we have the existing lease' do
+      lease = described_class.new(unique_key, timeout: 3600)
+      expect(lease.try_obtain).to be_present
+      expect(lease.renew).to be_truthy
+    end
+
+    it 'returns false when we dont have a lease' do
+      lease = described_class.new(unique_key, timeout: 3600)
+      expect(lease.renew).to be_falsey
+    end
+  end
+
   describe '#exists?' do
     it 'returns true for an existing lease' do
       lease = described_class.new(unique_key, timeout: 3600)
diff --git a/spec/lib/gitlab/fake_application_settings_spec.rb b/spec/lib/gitlab/fake_application_settings_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b793176d84a45fe5a70453c930d26b4b62c5f3f5
--- /dev/null
+++ b/spec/lib/gitlab/fake_application_settings_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe Gitlab::FakeApplicationSettings do
+  let(:defaults) { { signin_enabled: false, foobar: 'asdf', signup_enabled: true, 'test?' => 123 } }
+
+  subject { described_class.new(defaults) }
+
+  it 'wraps OpenStruct variables properly' do
+    expect(subject.signin_enabled).to be_falsey
+    expect(subject.signup_enabled).to be_truthy
+    expect(subject.foobar).to eq('asdf')
+  end
+
+  it 'defines predicate methods' do
+    expect(subject.signin_enabled?).to be_falsey
+    expect(subject.signup_enabled?).to be_truthy
+  end
+
+  it 'predicate method changes when value is updated' do
+    subject.signin_enabled = true
+
+    expect(subject.signin_enabled?).to be_truthy
+  end
+
+  it 'does not define a predicate method' do
+    expect(subject.foobar?).to be_nil
+  end
+
+  it 'does not override an existing predicate method' do
+    expect(subject.test?).to eq(123)
+  end
+end
diff --git a/spec/lib/gitlab/file_detector_spec.rb b/spec/lib/gitlab/file_detector_spec.rb
index e5ba13bbaf84bec9fda75257e7b6525d7037cc3d..695fd6f8573ae1e5cc8ecc9366f2a80d959e89d9 100644
--- a/spec/lib/gitlab/file_detector_spec.rb
+++ b/spec/lib/gitlab/file_detector_spec.rb
@@ -3,13 +3,13 @@ require 'spec_helper'
 describe Gitlab::FileDetector do
   describe '.types_in_paths' do
     it 'returns the file types for the given paths' do
-      expect(described_class.types_in_paths(%w(README.md CHANGELOG VERSION VERSION))).
-        to eq(%i{readme changelog version})
+      expect(described_class.types_in_paths(%w(README.md CHANGELOG VERSION VERSION)))
+        .to eq(%i{readme changelog version})
     end
 
     it 'does not include unrecognized file paths' do
-      expect(described_class.types_in_paths(%w(README.md foo.txt))).
-        to eq(%i{readme})
+      expect(described_class.types_in_paths(%w(README.md foo.txt)))
+        .to eq(%i{readme})
     end
   end
 
diff --git a/spec/lib/gitlab/git/attributes_spec.rb b/spec/lib/gitlab/git/attributes_spec.rb
index 1cfd8db09a5dab03eceb85b56b6c31ace08ee796..b715fc3410a196a14c0204c2bf431ff1b2fb9fa6 100644
--- a/spec/lib/gitlab/git/attributes_spec.rb
+++ b/spec/lib/gitlab/git/attributes_spec.rb
@@ -14,13 +14,13 @@ describe Gitlab::Git::Attributes, seed_helper: true do
       end
 
       it 'returns a Hash containing multiple attributes' do
-        expect(subject.attributes('test.sh')).
-          to eq({ 'eol' => 'lf', 'gitlab-language' => 'shell' })
+        expect(subject.attributes('test.sh'))
+          .to eq({ 'eol' => 'lf', 'gitlab-language' => 'shell' })
       end
 
       it 'returns a Hash containing attributes for a file with multiple extensions' do
-        expect(subject.attributes('test.haml.html')).
-          to eq({ 'gitlab-language' => 'haml' })
+        expect(subject.attributes('test.haml.html'))
+          .to eq({ 'gitlab-language' => 'haml' })
       end
 
       it 'returns a Hash containing attributes for a file in a directory' do
@@ -28,8 +28,8 @@ describe Gitlab::Git::Attributes, seed_helper: true do
       end
 
       it 'returns a Hash containing attributes with query string parameters' do
-        expect(subject.attributes('foo.cgi')).
-          to eq({ 'key' => 'value?p1=v1&p2=v2' })
+        expect(subject.attributes('foo.cgi'))
+          .to eq({ 'key' => 'value?p1=v1&p2=v2' })
       end
 
       it 'returns a Hash containing the attributes for an absolute path' do
@@ -39,11 +39,11 @@ describe Gitlab::Git::Attributes, seed_helper: true do
       it 'returns a Hash containing the attributes when a pattern is defined using an absolute path' do
         # When a path is given without a leading slash it should still match
         # patterns defined with a leading slash.
-        expect(subject.attributes('foo.png')).
-          to eq({ 'gitlab-language' => 'png' })
+        expect(subject.attributes('foo.png'))
+          .to eq({ 'gitlab-language' => 'png' })
 
-        expect(subject.attributes('/foo.png')).
-          to eq({ 'gitlab-language' => 'png' })
+        expect(subject.attributes('/foo.png'))
+          .to eq({ 'gitlab-language' => 'png' })
       end
 
       it 'returns an empty Hash for a defined path without attributes' do
@@ -74,8 +74,8 @@ describe Gitlab::Git::Attributes, seed_helper: true do
     end
 
     it 'parses an entry that uses a tab to separate the pattern and attributes' do
-      expect(subject.patterns[File.join(path, '*.md')]).
-        to eq({ 'gitlab-language' => 'markdown' })
+      expect(subject.patterns[File.join(path, '*.md')])
+        .to eq({ 'gitlab-language' => 'markdown' })
     end
 
     it 'stores patterns in reverse order' do
@@ -91,9 +91,9 @@ describe Gitlab::Git::Attributes, seed_helper: true do
     end
 
     it 'does not parse anything when the attributes file does not exist' do
-      expect(File).to receive(:exist?).
-        with(File.join(path, 'info/attributes')).
-        and_return(false)
+      expect(File).to receive(:exist?)
+        .with(File.join(path, 'info/attributes'))
+        .and_return(false)
 
       expect(subject.patterns).to eq({})
     end
@@ -115,13 +115,13 @@ describe Gitlab::Git::Attributes, seed_helper: true do
     it 'parses multiple attributes' do
       input = 'boolean key=value -negated'
 
-      expect(subject.parse_attributes(input)).
-        to eq({ 'boolean' => true, 'key' => 'value', 'negated' => false })
+      expect(subject.parse_attributes(input))
+        .to eq({ 'boolean' => true, 'key' => 'value', 'negated' => false })
     end
 
     it 'parses attributes with query string parameters' do
-      expect(subject.parse_attributes('foo=bar?baz=1')).
-        to eq({ 'foo' => 'bar?baz=1' })
+      expect(subject.parse_attributes('foo=bar?baz=1'))
+        .to eq({ 'foo' => 'bar?baz=1' })
     end
   end
 
@@ -133,9 +133,9 @@ describe Gitlab::Git::Attributes, seed_helper: true do
     end
 
     it 'does not yield when the attributes file does not exist' do
-      expect(File).to receive(:exist?).
-        with(File.join(path, 'info/attributes')).
-        and_return(false)
+      expect(File).to receive(:exist?)
+        .with(File.join(path, 'info/attributes'))
+        .and_return(false)
 
       expect { |b| subject.each_line(&b) }.not_to yield_control
     end
diff --git a/spec/lib/gitlab/git/blame_spec.rb b/spec/lib/gitlab/git/blame_spec.rb
index 8b041ac69b1d6e03d919409148177f7d821826b4..66c016d14b3a58ed5ad64c5890fc421ef5c5836d 100644
--- a/spec/lib/gitlab/git/blame_spec.rb
+++ b/spec/lib/gitlab/git/blame_spec.rb
@@ -20,6 +20,7 @@ describe Gitlab::Git::Blame, seed_helper: true do
       expect(data.size).to eq(95)
       expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit)
       expect(data.first[:line]).to eq("# Contribute to GitLab")
+      expect(data.first[:line]).to be_utf8
     end
   end
 
@@ -40,6 +41,7 @@ describe Gitlab::Git::Blame, seed_helper: true do
       expect(data.size).to eq(1)
       expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit)
       expect(data.first[:line]).to eq("Ä ü")
+      expect(data.first[:line]).to be_utf8
     end
   end
 
@@ -61,6 +63,7 @@ describe Gitlab::Git::Blame, seed_helper: true do
       expect(data.size).to eq(1)
       expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit)
       expect(data.first[:line]).to eq(" ")
+      expect(data.first[:line]).to be_utf8
     end
   end
 end
diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb
index e6a07a58d735e6ab0a826597febbcad121d888d9..58d3ee6b488a6074224ba1d7d281d204b64ece39 100644
--- a/spec/lib/gitlab/git/blob_spec.rb
+++ b/spec/lib/gitlab/git/blob_spec.rb
@@ -15,7 +15,7 @@ describe Gitlab::Git::Blob, seed_helper: true do
     end
   end
 
-  describe '.find' do
+  shared_examples 'finding blobs' do
     context 'file in subdir' do
       let(:blob) { Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, "files/ruby/popen.rb") }
 
@@ -92,15 +92,25 @@ describe Gitlab::Git::Blob, seed_helper: true do
       end
 
       it 'marks the blob as binary' do
-        expect(Gitlab::Git::Blob).to receive(:new).
-          with(hash_including(binary: true)).
-          and_call_original
+        expect(Gitlab::Git::Blob).to receive(:new)
+          .with(hash_including(binary: true))
+          .and_call_original
 
         expect(blob).to be_binary
       end
     end
   end
 
+  describe '.find' do
+    context 'when project_raw_show Gitaly feature is enabled' do
+      it_behaves_like 'finding blobs'
+    end
+
+    context 'when project_raw_show Gitaly feature is disabled', skip_gitaly_mock: true do
+      it_behaves_like 'finding blobs'
+    end
+  end
+
   describe '.raw' do
     let(:raw_blob) { Gitlab::Git::Blob.raw(repository, SeedRepo::RubyBlob::ID) }
     it { expect(raw_blob.id).to eq(SeedRepo::RubyBlob::ID) }
diff --git a/spec/lib/gitlab/git/branch_spec.rb b/spec/lib/gitlab/git/branch_spec.rb
index 9eac7660cd1430dcfc5640500ac89105ea9995c2..d1d7ed1d02a0655e295590f74dd7119d655e0d5b 100644
--- a/spec/lib/gitlab/git/branch_spec.rb
+++ b/spec/lib/gitlab/git/branch_spec.rb
@@ -45,10 +45,10 @@ describe Gitlab::Git::Branch, seed_helper: true do
     let(:branch) { described_class.new(repository, 'foo', gitaly_branch) }
 
     it 'parses Gitaly::FindLocalBranchResponse correctly' do
-      expect(Gitlab::Git::Commit).to receive(:decorate).
-        with(hash_including(attributes)).and_call_original
+      expect(Gitlab::Git::Commit).to receive(:decorate)
+        .with(hash_including(attributes)).and_call_original
 
-      expect(branch.dereferenced_target.message.encoding).to be(Encoding::UTF_8)
+      expect(branch.dereferenced_target.message).to be_utf8
     end
   end
 
diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb
index 3e44c57764366297170f819e995ffa18d65bc407..f20a14155dc02b4adf516a188960e5fd4edaedd7 100644
--- a/spec/lib/gitlab/git/commit_spec.rb
+++ b/spec/lib/gitlab/git/commit_spec.rb
@@ -244,6 +244,33 @@ describe Gitlab::Git::Commit, seed_helper: true do
     end
 
     describe '.find_all' do
+      it 'should return a return a collection of commits' do
+        commits = described_class.find_all(repository)
+
+        expect(commits).not_to be_empty
+        expect(commits).to all( be_a_kind_of(Gitlab::Git::Commit) )
+      end
+
+      context 'while applying a sort order based on the `order` option' do
+        it "allows ordering topologically (no parents shown before their children)" do
+          expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_TOPO)
+
+          described_class.find_all(repository, order: :topo)
+        end
+
+        it "allows ordering by date" do
+          expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_DATE | Rugged::SORT_TOPO)
+
+          described_class.find_all(repository, order: :date)
+        end
+
+        it "applies no sorting by default" do
+          expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_NONE)
+
+          described_class.find_all(repository)
+        end
+      end
+
       context 'max_count' do
         subject do
           commits = Gitlab::Git::Commit.find_all(
@@ -281,26 +308,6 @@ describe Gitlab::Git::Commit, seed_helper: true do
         it { is_expected.to include(SeedRepo::FirstCommit::ID) }
         it { is_expected.not_to include(SeedRepo::LastCommit::ID) }
       end
-
-      context 'contains feature + max_count' do
-        subject do
-          commits = Gitlab::Git::Commit.find_all(
-            repository,
-            contains: 'feature',
-            max_count: 7
-          )
-
-          commits.map { |c| c.id }
-        end
-
-        it 'has 7 elements' do
-          expect(subject.size).to eq(7)
-        end
-
-        it { is_expected.not_to include(SeedRepo::Commit::PARENT_ID) }
-        it { is_expected.not_to include(SeedRepo::Commit::ID) }
-        it { is_expected.to include(SeedRepo::BigCommit::ID) }
-      end
     end
   end
 
diff --git a/spec/lib/gitlab/git/diff_collection_spec.rb b/spec/lib/gitlab/git/diff_collection_spec.rb
index a9a7bba2c054701e4f63910ac282616ff3ebde73..d20298fa1398cc1a740a9237caee6b419e7be341 100644
--- a/spec/lib/gitlab/git/diff_collection_spec.rb
+++ b/spec/lib/gitlab/git/diff_collection_spec.rb
@@ -325,8 +325,8 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
       end
 
       it 'yields Diff instances even when they are too large' do
-        expect { |b| collection.each(&b) }.
-          to yield_with_args(an_instance_of(Gitlab::Git::Diff))
+        expect { |b| collection.each(&b) }
+          .to yield_with_args(an_instance_of(Gitlab::Git::Diff))
       end
 
       it 'prunes diffs that are too large' do
@@ -348,8 +348,8 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
         let(:expanded) { true }
 
         it 'yields Diff instances even when they are quite big' do
-          expect { |b| subject.each(&b) }.
-            to yield_with_args(an_instance_of(Gitlab::Git::Diff))
+          expect { |b| subject.each(&b) }
+            .to yield_with_args(an_instance_of(Gitlab::Git::Diff))
         end
 
         it 'does not prune diffs' do
@@ -367,8 +367,8 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
         let(:expanded) { false }
 
         it 'yields Diff instances even when they are quite big' do
-          expect { |b| subject.each(&b) }.
-            to yield_with_args(an_instance_of(Gitlab::Git::Diff))
+          expect { |b| subject.each(&b) }
+            .to yield_with_args(an_instance_of(Gitlab::Git::Diff))
         end
 
         it 'prunes diffs that are quite big' do
@@ -454,8 +454,8 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
         let(:limits) { false }
 
         it 'yields Diff instances even when they are quite big' do
-          expect { |b| subject.each(&b) }.
-            to yield_with_args(an_instance_of(Gitlab::Git::Diff))
+          expect { |b| subject.each(&b) }
+            .to yield_with_args(an_instance_of(Gitlab::Git::Diff))
         end
 
         it 'does not prune diffs' do
diff --git a/spec/lib/gitlab/git/diff_spec.rb b/spec/lib/gitlab/git/diff_spec.rb
index da213f617cc92ab2dc502a3501e3fa8e203d0728..d97e85364c2114cb734c6a210902ec0404e1e0a9 100644
--- a/spec/lib/gitlab/git/diff_spec.rb
+++ b/spec/lib/gitlab/git/diff_spec.rb
@@ -90,7 +90,7 @@ EOT
         let(:diff) { described_class.new(@rugged_diff) }
 
         it 'initializes the diff' do
-          expect(diff.to_hash).to eq(@raw_diff_hash.merge(too_large: nil))
+          expect(diff.to_hash).to eq(@raw_diff_hash)
         end
 
         it 'does not prune the diff' do
@@ -100,8 +100,8 @@ EOT
 
       context 'using a diff that is too large' do
         it 'prunes the diff' do
-          expect_any_instance_of(String).to receive(:bytesize).
-            and_return(1024 * 1024 * 1024)
+          expect_any_instance_of(String).to receive(:bytesize)
+            .and_return(1024 * 1024 * 1024)
 
           diff = described_class.new(@rugged_diff)
 
@@ -130,8 +130,8 @@ EOT
 
       context 'using a large binary diff' do
         it 'does not prune the diff' do
-          expect_any_instance_of(Rugged::Diff::Delta).to receive(:binary?).
-            and_return(true)
+          expect_any_instance_of(Rugged::Diff::Delta).to receive(:binary?)
+            .and_return(true)
 
           diff = described_class.new(@rugged_diff)
 
@@ -175,6 +175,14 @@ EOT
           expect(diff).to be_too_large
         end
       end
+
+      context 'when the patch passed is not UTF-8-encoded' do
+        let(:raw_patch) { @raw_diff_hash[:diff].encode(Encoding::ASCII_8BIT) }
+
+        it 'encodes diff patch to UTF-8' do
+          expect(diff.diff).to be_utf8
+        end
+      end
     end
   end
 
diff --git a/spec/lib/gitlab/git/gitmodules_parser_spec.rb b/spec/lib/gitlab/git/gitmodules_parser_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..143aa2218c9a4771d01d353d2273e02b6d1833ea
--- /dev/null
+++ b/spec/lib/gitlab/git/gitmodules_parser_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+describe Gitlab::Git::GitmodulesParser do
+  it 'should parse a .gitmodules file correctly' do
+    parser = described_class.new(<<-'GITMODULES'.strip_heredoc)
+      [submodule "vendor/libgit2"]
+         path = vendor/libgit2
+      [submodule "vendor/libgit2"]
+         url = https://github.com/nodegit/libgit2.git
+
+      # a comment
+      [submodule "moved"]
+          path = new/path
+          url = https://example.com/some/project
+      [submodule "bogus"]
+          url = https://example.com/another/project
+    GITMODULES
+
+    modules = parser.parse
+
+    expect(modules).to eq({
+                            'vendor/libgit2' => { 'name' => 'vendor/libgit2',
+                                                  'url' => 'https://github.com/nodegit/libgit2.git' },
+                            'new/path' => { 'name' => 'moved',
+                                            'url' => 'https://example.com/some/project' }
+                          })
+  end
+end
diff --git a/spec/lib/gitlab/git/hook_spec.rb b/spec/lib/gitlab/git/hook_spec.rb
index 3f279c2186546a1b08c19b5c25457d1d19caf870..73518656bde259bac6ca95ed230941f94c16f35e 100644
--- a/spec/lib/gitlab/git/hook_spec.rb
+++ b/spec/lib/gitlab/git/hook_spec.rb
@@ -4,18 +4,20 @@ require 'fileutils'
 describe Gitlab::Git::Hook, lib: true do
   describe "#trigger" do
     let(:project) { create(:project, :repository) }
+    let(:repo_path) { project.repository.path }
     let(:user) { create(:user) }
+    let(:gl_id) { Gitlab::GlId.gl_id(user) }
 
     def create_hook(name)
-      FileUtils.mkdir_p(File.join(project.repository.path, 'hooks'))
-      File.open(File.join(project.repository.path, 'hooks', name), 'w', 0755) do |f|
+      FileUtils.mkdir_p(File.join(repo_path, 'hooks'))
+      File.open(File.join(repo_path, 'hooks', name), 'w', 0755) do |f|
         f.write('exit 0')
       end
     end
 
     def create_failing_hook(name)
-      FileUtils.mkdir_p(File.join(project.repository.path, 'hooks'))
-      File.open(File.join(project.repository.path, 'hooks', name), 'w', 0755) do |f|
+      FileUtils.mkdir_p(File.join(repo_path, 'hooks'))
+      File.open(File.join(repo_path, 'hooks', name), 'w', 0755) do |f|
         f.write(<<-HOOK)
           echo 'regular message from the hook'
           echo 'error message from the hook' 1>&2
@@ -27,13 +29,29 @@ describe Gitlab::Git::Hook, lib: true do
     ['pre-receive', 'post-receive', 'update'].each do |hook_name|
       context "when triggering a #{hook_name} hook" do
         context "when the hook is successful" do
+          let(:hook_path) { File.join(repo_path, 'hooks', hook_name) }
+          let(:gl_repository) { Gitlab::GlRepository.gl_repository(project, false) }
+          let(:env) do
+            {
+              'GL_ID' => gl_id,
+              'PWD' => repo_path,
+              'GL_PROTOCOL' => 'web',
+              'GL_REPOSITORY' => gl_repository
+            }
+          end
+
           it "returns success with no errors" do
             create_hook(hook_name)
-            hook = Gitlab::Git::Hook.new(hook_name, project.repository.path)
+            hook = Gitlab::Git::Hook.new(hook_name, project)
             blank = Gitlab::Git::BLANK_SHA
             ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch'
 
-            status, errors = hook.trigger(Gitlab::GlId.gl_id(user), blank, blank, ref)
+            if hook_name != 'update'
+              expect(Open3).to receive(:popen3)
+                .with(env, hook_path, chdir: repo_path).and_call_original
+            end
+
+            status, errors = hook.trigger(gl_id, blank, blank, ref)
             expect(status).to be true
             expect(errors).to be_blank
           end
@@ -42,11 +60,11 @@ describe Gitlab::Git::Hook, lib: true do
         context "when the hook is unsuccessful" do
           it "returns failure with errors" do
             create_failing_hook(hook_name)
-            hook = Gitlab::Git::Hook.new(hook_name, project.repository.path)
+            hook = Gitlab::Git::Hook.new(hook_name, project)
             blank = Gitlab::Git::BLANK_SHA
             ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch'
 
-            status, errors = hook.trigger(Gitlab::GlId.gl_id(user), blank, blank, ref)
+            status, errors = hook.trigger(gl_id, blank, blank, ref)
             expect(status).to be false
             expect(errors).to eq("error message from the hook\n")
           end
@@ -56,11 +74,11 @@ describe Gitlab::Git::Hook, lib: true do
 
     context "when the hook doesn't exist" do
       it "returns success with no errors" do
-        hook = Gitlab::Git::Hook.new('unknown_hook', project.repository.path)
+        hook = Gitlab::Git::Hook.new('unknown_hook', project)
         blank = Gitlab::Git::BLANK_SHA
         ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch'
 
-        status, errors = hook.trigger(Gitlab::GlId.gl_id(user), blank, blank, ref)
+        status, errors = hook.trigger(gl_id, blank, blank, ref)
         expect(status).to be true
         expect(errors).to be_nil
       end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index eee4c9eab6da5abc9940dec0fd9c9cc9c186852d..6aca181194aabd71273c8a3b897f8f7be011d09b 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -26,31 +26,25 @@ describe Gitlab::Git::Repository, seed_helper: true do
       end
     end
 
-    context 'with gitaly enabled' do
-      before do
-        stub_gitaly
-      end
-
-      after do
-        Gitlab::GitalyClient.clear_stubs!
-      end
+    it 'returns UTF-8' do
+      expect(repository.root_ref).to be_utf8
+    end
 
-      it 'gets the branch name from GitalyClient' do
-        expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name)
-        repository.root_ref
-      end
+    it 'gets the branch name from GitalyClient' do
+      expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name)
+      repository.root_ref
+    end
 
-      it 'wraps GRPC not found' do
-        expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name).
-          and_raise(GRPC::NotFound)
-        expect { repository.root_ref }.to raise_error(Gitlab::Git::Repository::NoRepository)
-      end
+    it 'wraps GRPC not found' do
+      expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name)
+        .and_raise(GRPC::NotFound)
+      expect { repository.root_ref }.to raise_error(Gitlab::Git::Repository::NoRepository)
+    end
 
-      it 'wraps GRPC exceptions' do
-        expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name).
-          and_raise(GRPC::Unknown)
-        expect { repository.root_ref }.to raise_error(Gitlab::Git::CommandError)
-      end
+    it 'wraps GRPC exceptions' do
+      expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name)
+        .and_raise(GRPC::Unknown)
+      expect { repository.root_ref }.to raise_error(Gitlab::Git::CommandError)
     end
   end
 
@@ -123,34 +117,29 @@ describe Gitlab::Git::Repository, seed_helper: true do
     it 'has SeedRepo::Repo::BRANCHES.size elements' do
       expect(subject.size).to eq(SeedRepo::Repo::BRANCHES.size)
     end
-    it { is_expected.to include("master") }
-    it { is_expected.not_to include("branch-from-space") }
 
-    context 'with gitaly enabled' do
-      before do
-        stub_gitaly
-      end
+    it 'returns UTF-8' do
+      expect(subject.first).to be_utf8
+    end
 
-      after do
-        Gitlab::GitalyClient.clear_stubs!
-      end
+    it { is_expected.to include("master") }
+    it { is_expected.not_to include("branch-from-space") }
 
-      it 'gets the branch names from GitalyClient' do
-        expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names)
-        subject
-      end
+    it 'gets the branch names from GitalyClient' do
+      expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names)
+      subject
+    end
 
-      it 'wraps GRPC not found' do
-        expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names).
-          and_raise(GRPC::NotFound)
-        expect { subject }.to raise_error(Gitlab::Git::Repository::NoRepository)
-      end
+    it 'wraps GRPC not found' do
+      expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names)
+        .and_raise(GRPC::NotFound)
+      expect { subject }.to raise_error(Gitlab::Git::Repository::NoRepository)
+    end
 
-      it 'wraps GRPC other exceptions' do
-        expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names).
-          and_raise(GRPC::Unknown)
-        expect { subject }.to raise_error(Gitlab::Git::CommandError)
-      end
+    it 'wraps GRPC other exceptions' do
+      expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names)
+        .and_raise(GRPC::Unknown)
+      expect { subject }.to raise_error(Gitlab::Git::CommandError)
     end
   end
 
@@ -158,10 +147,15 @@ describe Gitlab::Git::Repository, seed_helper: true do
     subject { repository.tag_names }
 
     it { is_expected.to be_kind_of Array }
+
     it 'has SeedRepo::Repo::TAGS.size elements' do
       expect(subject.size).to eq(SeedRepo::Repo::TAGS.size)
     end
 
+    it 'returns UTF-8' do
+      expect(subject.first).to be_utf8
+    end
+
     describe '#last' do
       subject { super().last }
       it { is_expected.to eq("v1.2.1") }
@@ -169,31 +163,21 @@ describe Gitlab::Git::Repository, seed_helper: true do
     it { is_expected.to include("v1.0.0") }
     it { is_expected.not_to include("v5.0.0") }
 
-    context 'with gitaly enabled' do
-      before do
-        stub_gitaly
-      end
-
-      after do
-        Gitlab::GitalyClient.clear_stubs!
-      end
-
-      it 'gets the tag names from GitalyClient' do
-        expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names)
-        subject
-      end
+    it 'gets the tag names from GitalyClient' do
+      expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names)
+      subject
+    end
 
-      it 'wraps GRPC not found' do
-        expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names).
-          and_raise(GRPC::NotFound)
-        expect { subject }.to raise_error(Gitlab::Git::Repository::NoRepository)
-      end
+    it 'wraps GRPC not found' do
+      expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names)
+        .and_raise(GRPC::NotFound)
+      expect { subject }.to raise_error(Gitlab::Git::Repository::NoRepository)
+    end
 
-      it 'wraps GRPC exceptions' do
-        expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names).
-          and_raise(GRPC::Unknown)
-        expect { subject }.to raise_error(Gitlab::Git::CommandError)
-      end
+    it 'wraps GRPC exceptions' do
+      expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names)
+        .and_raise(GRPC::Unknown)
+      expect { subject }.to raise_error(Gitlab::Git::CommandError)
     end
   end
 
@@ -344,11 +328,43 @@ describe Gitlab::Git::Repository, seed_helper: true do
     end
   end
 
+  describe '#submodule_url_for' do
+    let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) }
+    let(:ref) { 'master' }
+
+    def submodule_url(path)
+      repository.submodule_url_for(ref, path)
+    end
+
+    it { expect(submodule_url('six')).to eq('git://github.com/randx/six.git') }
+    it { expect(submodule_url('nested/six')).to eq('git://github.com/randx/six.git') }
+    it { expect(submodule_url('deeper/nested/six')).to eq('git://github.com/randx/six.git') }
+    it { expect(submodule_url('invalid/path')).to eq(nil) }
+
+    context 'uncommitted submodule dir' do
+      let(:ref) { 'fix-existing-submodule-dir' }
+
+      it { expect(submodule_url('submodule-existing-dir')).to eq(nil) }
+    end
+
+    context 'tags' do
+      let(:ref) { 'v1.2.1' }
+
+      it { expect(submodule_url('six')).to eq('git://github.com/randx/six.git') }
+    end
+
+    context 'no submodules at commit' do
+      let(:ref) { '6d39438' }
+
+      it { expect(submodule_url('six')).to eq(nil) }
+    end
+  end
+
   context '#submodules' do
     let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) }
 
     context 'where repo has submodules' do
-      let(:submodules) { repository.submodules('master') }
+      let(:submodules) { repository.send(:submodules, 'master') }
       let(:submodule) { submodules.first }
 
       it { expect(submodules).to be_kind_of Hash }
@@ -358,7 +374,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
         expect(submodule).to eq([
           "six", {
             "id" => "409f37c4f05865e4fb208c771485f211a22c4c2d",
-            "path" => "six",
+            "name" => "six",
             "url" => "git://github.com/randx/six.git"
           }
         ])
@@ -366,14 +382,14 @@ describe Gitlab::Git::Repository, seed_helper: true do
 
       it 'should handle nested submodules correctly' do
         nested = submodules['nested/six']
-        expect(nested['path']).to eq('nested/six')
+        expect(nested['name']).to eq('nested/six')
         expect(nested['url']).to eq('git://github.com/randx/six.git')
         expect(nested['id']).to eq('24fb71c79fcabc63dfd8832b12ee3bf2bf06b196')
       end
 
       it 'should handle deeply nested submodules correctly' do
         nested = submodules['deeper/nested/six']
-        expect(nested['path']).to eq('deeper/nested/six')
+        expect(nested['name']).to eq('deeper/nested/six')
         expect(nested['url']).to eq('git://github.com/randx/six.git')
         expect(nested['id']).to eq('24fb71c79fcabc63dfd8832b12ee3bf2bf06b196')
       end
@@ -383,17 +399,17 @@ describe Gitlab::Git::Repository, seed_helper: true do
       end
 
       it 'should not have an entry for an uncommited submodule dir' do
-        submodules = repository.submodules('fix-existing-submodule-dir')
+        submodules = repository.send(:submodules, 'fix-existing-submodule-dir')
         expect(submodules).not_to have_key('submodule-existing-dir')
       end
 
       it 'should handle tags correctly' do
-        submodules = repository.submodules('v1.2.1')
+        submodules = repository.send(:submodules, 'v1.2.1')
 
         expect(submodules.first).to eq([
           "six", {
             "id" => "409f37c4f05865e4fb208c771485f211a22c4c2d",
-            "path" => "six",
+            "name" => "six",
             "url" => "git://github.com/randx/six.git"
           }
         ])
@@ -414,7 +430,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
     end
 
     context 'where repo doesn\'t have submodules' do
-      let(:submodules) { repository.submodules('6d39438') }
+      let(:submodules) { repository.send(:submodules, '6d39438') }
       it 'should return an empty hash' do
         expect(submodules).to be_empty
       end
@@ -472,8 +488,8 @@ describe Gitlab::Git::Repository, seed_helper: true do
       end
 
       it "should move the tip of the master branch to the correct commit" do
-        new_tip = @normal_repo.rugged.references["refs/heads/master"].
-          target.oid
+        new_tip = @normal_repo.rugged.references["refs/heads/master"]
+          .target.oid
 
         expect(new_tip).to eq(reset_commit)
       end
@@ -1101,35 +1117,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
     end
   end
 
-  describe '#find_commits' do
-    it 'should return a return a collection of commits' do
-      commits = repository.find_commits
-
-      expect(commits).not_to be_empty
-      expect(commits).to all( be_a_kind_of(Gitlab::Git::Commit) )
-    end
-
-    context 'while applying a sort order based on the `order` option' do
-      it "allows ordering topologically (no parents shown before their children)" do
-        expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_TOPO)
-
-        repository.find_commits(order: :topo)
-      end
-
-      it "allows ordering by date" do
-        expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_DATE | Rugged::SORT_TOPO)
-
-        repository.find_commits(order: :date)
-      end
-
-      it "applies no sorting by default" do
-        expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_NONE)
-
-        repository.find_commits
-      end
-    end
-  end
-
   describe '#branches with deleted branch' do
     before(:each) do
       ref = double()
@@ -1296,32 +1283,31 @@ describe Gitlab::Git::Repository, seed_helper: true do
       expect(@repo.local_branches.any? { |branch| branch.name == 'local_branch' }).to eq(true)
     end
 
-    context 'with gitaly enabled' do
-      before do
-        stub_gitaly
-      end
-
-      after do
-        Gitlab::GitalyClient.clear_stubs!
+    it 'returns a Branch with UTF-8 fields' do
+      branches = @repo.local_branches.to_a
+      expect(branches.size).to be > 0
+      branches.each do |branch|
+        expect(branch.name).to be_utf8
+        expect(branch.target).to be_utf8 unless branch.target.nil?
       end
+    end
 
-      it 'gets the branches from GitalyClient' do
-        expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches).
-          and_return([])
-        @repo.local_branches
-      end
+    it 'gets the branches from GitalyClient' do
+      expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches)
+        .and_return([])
+      @repo.local_branches
+    end
 
-      it 'wraps GRPC not found' do
-        expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches).
-          and_raise(GRPC::NotFound)
-        expect { @repo.local_branches }.to raise_error(Gitlab::Git::Repository::NoRepository)
-      end
+    it 'wraps GRPC not found' do
+      expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches)
+        .and_raise(GRPC::NotFound)
+      expect { @repo.local_branches }.to raise_error(Gitlab::Git::Repository::NoRepository)
+    end
 
-      it 'wraps GRPC exceptions' do
-        expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches).
-          and_raise(GRPC::Unknown)
-        expect { @repo.local_branches }.to raise_error(Gitlab::Git::CommandError)
-      end
+    it 'wraps GRPC exceptions' do
+      expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches)
+        .and_raise(GRPC::Unknown)
+      expect { @repo.local_branches }.to raise_error(Gitlab::Git::CommandError)
     end
   end
 
@@ -1400,11 +1386,4 @@ describe Gitlab::Git::Repository, seed_helper: true do
     sha = Rugged::Commit.create(repo, options)
     repo.lookup(sha)
   end
-
-  def stub_gitaly
-    allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(true)
-
-    stub = double(:stub)
-    allow(Gitaly::Ref::Stub).to receive(:new).and_return(stub)
-  end
 end
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index 3dcc20c48e8a1ad0297a95a45378111fc13d0326..9a86cfa66e4831c2116e4112c81230225fa12e3d 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -3,11 +3,12 @@ require 'spec_helper'
 describe Gitlab::GitAccess, lib: true do
   let(:pull_access_check) { access.check('git-upload-pack', '_any') }
   let(:push_access_check) { access.check('git-receive-pack', '_any') }
-  let(:access) { Gitlab::GitAccess.new(actor, project, protocol, authentication_abilities: authentication_abilities) }
+  let(:access) { Gitlab::GitAccess.new(actor, project, protocol, authentication_abilities: authentication_abilities, redirected_path: redirected_path) }
   let(:project) { create(:project, :repository) }
   let(:user) { create(:user) }
   let(:actor) { user }
   let(:protocol) { 'ssh' }
+  let(:redirected_path) { nil }
   let(:authentication_abilities) do
     [
       :read_project,
@@ -162,6 +163,46 @@ describe Gitlab::GitAccess, lib: true do
     end
   end
 
+  describe '#check_project_moved!' do
+    before do
+      project.team << [user, :master]
+    end
+
+    context 'when a redirect was not followed to find the project' do
+      context 'pull code' do
+        it { expect { pull_access_check }.not_to raise_error }
+      end
+
+      context 'push code' do
+        it { expect { push_access_check }.not_to raise_error }
+      end
+    end
+
+    context 'when a redirect was followed to find the project' do
+      let(:redirected_path) { 'some/other-path' }
+
+      context 'pull code' do
+        it { expect { pull_access_check }.to raise_not_found(/Project '#{redirected_path}' was moved to '#{project.full_path}'/) }
+        it { expect { pull_access_check }.to raise_not_found(/git remote set-url origin #{project.ssh_url_to_repo}/) }
+
+        context 'http protocol' do
+          let(:protocol) { 'http' }
+          it { expect { pull_access_check }.to raise_not_found(/git remote set-url origin #{project.http_url_to_repo}/) }
+        end
+      end
+
+      context 'push code' do
+        it { expect { push_access_check }.to raise_not_found(/Project '#{redirected_path}' was moved to '#{project.full_path}'/) }
+        it { expect { push_access_check }.to raise_not_found(/git remote set-url origin #{project.ssh_url_to_repo}/) }
+
+        context 'http protocol' do
+          let(:protocol) { 'http' }
+          it { expect { push_access_check }.to raise_not_found(/git remote set-url origin #{project.http_url_to_repo}/) }
+        end
+      end
+    end
+  end
+
   describe '#check_command_disabled!' do
     before do
       project.team << [user, :master]
diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb
index a1eb95750ba314b9c777a08803906dd449a5634a..797ec8cb23ebfa8771ffd0d38235c6af3edfd149 100644
--- a/spec/lib/gitlab/git_access_wiki_spec.rb
+++ b/spec/lib/gitlab/git_access_wiki_spec.rb
@@ -1,9 +1,10 @@
 require 'spec_helper'
 
 describe Gitlab::GitAccessWiki, lib: true do
-  let(:access) { Gitlab::GitAccessWiki.new(user, project, 'web', authentication_abilities: authentication_abilities) }
+  let(:access) { Gitlab::GitAccessWiki.new(user, project, 'web', authentication_abilities: authentication_abilities, redirected_path: redirected_path) }
   let(:project) { create(:project, :repository) }
   let(:user) { create(:user) }
+  let(:redirected_path) { nil }
   let(:authentication_abilities) do
     [
       :read_project,
diff --git a/spec/lib/gitlab/gitaly_client/commit_spec.rb b/spec/lib/gitlab/gitaly_client/commit_spec.rb
index cf1bc74779e170c8ff1bf7252efcb250042d2399..dff5b25c7129bb5d8b66698b76005e166091d3a2 100644
--- a/spec/lib/gitlab/gitaly_client/commit_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/commit_spec.rb
@@ -16,7 +16,7 @@ describe Gitlab::GitalyClient::Commit do
           right_commit_id: commit.id
         )
 
-        expect_any_instance_of(Gitaly::Diff::Stub).to receive(:commit_diff).with(request)
+        expect_any_instance_of(Gitaly::Diff::Stub).to receive(:commit_diff).with(request, kind_of(Hash))
 
         described_class.new(repository).diff_from_parent(commit)
       end
@@ -31,7 +31,7 @@ describe Gitlab::GitalyClient::Commit do
           right_commit_id: initial_commit.id
         )
 
-        expect_any_instance_of(Gitaly::Diff::Stub).to receive(:commit_diff).with(request)
+        expect_any_instance_of(Gitaly::Diff::Stub).to receive(:commit_diff).with(request, kind_of(Hash))
 
         described_class.new(repository).diff_from_parent(initial_commit)
       end
@@ -61,7 +61,7 @@ describe Gitlab::GitalyClient::Commit do
           right_commit_id: commit.id
         )
 
-        expect_any_instance_of(Gitaly::Diff::Stub).to receive(:commit_delta).with(request).and_return([])
+        expect_any_instance_of(Gitaly::Diff::Stub).to receive(:commit_delta).with(request, kind_of(Hash)).and_return([])
 
         described_class.new(repository).commit_deltas(commit)
       end
@@ -76,7 +76,7 @@ describe Gitlab::GitalyClient::Commit do
           right_commit_id: initial_commit.id
         )
 
-        expect_any_instance_of(Gitaly::Diff::Stub).to receive(:commit_delta).with(request).and_return([])
+        expect_any_instance_of(Gitaly::Diff::Stub).to receive(:commit_delta).with(request, kind_of(Hash)).and_return([])
 
         described_class.new(repository).commit_deltas(initial_commit)
       end
diff --git a/spec/lib/gitlab/gitaly_client/notifications_spec.rb b/spec/lib/gitlab/gitaly_client/notifications_spec.rb
index e5c9e06a15e95c003d8caeec90568d1c09763ca4..7404ffe0f06ccf799ba1eae155df01dab458eb1a 100644
--- a/spec/lib/gitlab/gitaly_client/notifications_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/notifications_spec.rb
@@ -8,8 +8,8 @@ describe Gitlab::GitalyClient::Notifications do
     subject { described_class.new(project.repository) }
 
     it 'sends a post_receive message' do
-      expect_any_instance_of(Gitaly::Notifications::Stub).
-        to receive(:post_receive).with(gitaly_request_with_path(storage_name, relative_path))
+      expect_any_instance_of(Gitaly::Notifications::Stub)
+        .to receive(:post_receive).with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
 
       subject.post_receive
     end
diff --git a/spec/lib/gitlab/gitaly_client/ref_spec.rb b/spec/lib/gitlab/gitaly_client/ref_spec.rb
index 2ea44ef74b060507f7089ad993a102972fd75f8c..7c0904607648cc0e0df2cfa0f30d88116124a24c 100644
--- a/spec/lib/gitlab/gitaly_client/ref_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/ref_spec.rb
@@ -6,23 +6,12 @@ describe Gitlab::GitalyClient::Ref do
   let(:relative_path) { project.path_with_namespace + '.git' }
   let(:client) { described_class.new(project.repository) }
 
-  before do
-    allow(Gitlab.config.gitaly).to receive(:enabled).and_return(true)
-  end
-
-  after do
-    # When we say `expect_any_instance_of(Gitaly::Ref::Stub)` a double is created,
-    # and because GitalyClient shares stubs these will get passed from example to
-    # example, which will cause an error, so we clean the stubs after each example.
-    Gitlab::GitalyClient.clear_stubs!
-  end
-
   describe '#branch_names' do
     it 'sends a find_all_branch_names message' do
-      expect_any_instance_of(Gitaly::Ref::Stub).
-        to receive(:find_all_branch_names).
-          with(gitaly_request_with_path(storage_name, relative_path)).
-          and_return([])
+      expect_any_instance_of(Gitaly::Ref::Stub)
+        .to receive(:find_all_branch_names)
+        .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
+        .and_return([])
 
       client.branch_names
     end
@@ -30,10 +19,10 @@ describe Gitlab::GitalyClient::Ref do
 
   describe '#tag_names' do
     it 'sends a find_all_tag_names message' do
-      expect_any_instance_of(Gitaly::Ref::Stub).
-        to receive(:find_all_tag_names).
-          with(gitaly_request_with_path(storage_name, relative_path)).
-          and_return([])
+      expect_any_instance_of(Gitaly::Ref::Stub)
+        .to receive(:find_all_tag_names)
+        .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
+        .and_return([])
 
       client.tag_names
     end
@@ -41,10 +30,10 @@ describe Gitlab::GitalyClient::Ref do
 
   describe '#default_branch_name' do
     it 'sends a find_default_branch_name message' do
-      expect_any_instance_of(Gitaly::Ref::Stub).
-        to receive(:find_default_branch_name).
-          with(gitaly_request_with_path(storage_name, relative_path)).
-        and_return(double(name: 'foo'))
+      expect_any_instance_of(Gitaly::Ref::Stub)
+        .to receive(:find_default_branch_name)
+        .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
+        .and_return(double(name: 'foo'))
 
       client.default_branch_name
     end
@@ -52,25 +41,43 @@ describe Gitlab::GitalyClient::Ref do
 
   describe '#local_branches' do
     it 'sends a find_local_branches message' do
-      expect_any_instance_of(Gitaly::Ref::Stub).
-        to receive(:find_local_branches).
-          with(gitaly_request_with_path(storage_name, relative_path)).
-          and_return([])
+      expect_any_instance_of(Gitaly::Ref::Stub)
+        .to receive(:find_local_branches)
+        .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
+        .and_return([])
 
       client.local_branches
     end
 
     it 'parses and sends the sort parameter' do
-      expect_any_instance_of(Gitaly::Ref::Stub).
-        to receive(:find_local_branches).
-          with(gitaly_request_with_params(sort_by: :UPDATED_DESC)).
-          and_return([])
+      expect_any_instance_of(Gitaly::Ref::Stub)
+        .to receive(:find_local_branches)
+        .with(gitaly_request_with_params(sort_by: :UPDATED_DESC), kind_of(Hash))
+        .and_return([])
 
       client.local_branches(sort_by: 'updated_desc')
     end
 
+    it 'translates known mismatches on sort param values' do
+      expect_any_instance_of(Gitaly::Ref::Stub)
+        .to receive(:find_local_branches)
+        .with(gitaly_request_with_params(sort_by: :NAME), kind_of(Hash))
+        .and_return([])
+
+      client.local_branches(sort_by: 'name_asc')
+    end
+
     it 'raises an argument error if an invalid sort_by parameter is passed' do
       expect { client.local_branches(sort_by: 'invalid_sort') }.to raise_error(ArgumentError)
     end
   end
+
+  describe '#find_ref_name', seed_helper: true do
+    let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) }
+    let(:client) { described_class.new(repository) }
+    subject { client.find_ref_name(SeedRepo::Commit::ID, 'refs/heads/master') }
+
+    it { is_expected.to be_utf8 }
+    it { is_expected.to eq('refs/heads/master') }
+  end
 end
diff --git a/spec/lib/gitlab/gitlab_import/importer_spec.rb b/spec/lib/gitlab/gitlab_import/importer_spec.rb
index 9b499b593d32ced37b99ab25a72b56699ac2c1df..4f588da0a8350859e593608932eb2a576c802e78 100644
--- a/spec/lib/gitlab/gitlab_import/importer_spec.rb
+++ b/spec/lib/gitlab/gitlab_import/importer_spec.rb
@@ -45,8 +45,8 @@ describe Gitlab::GitlabImport::Importer, lib: true do
     def stub_request(path, body)
       url = "https://gitlab.com/api/v3/projects/asd%2Fvim/#{path}?page=1&per_page=100"
 
-      WebMock.stub_request(:get, url).
-        to_return(
+      WebMock.stub_request(:get, url)
+        .to_return(
           headers: { 'Content-Type' => 'application/json' },
           body: body
         )
diff --git a/spec/lib/gitlab/group_hierarchy_spec.rb b/spec/lib/gitlab/group_hierarchy_spec.rb
index 5d0ed1522b39e391dfbfa544b6be4655dd7c1798..08010c2d0e222f1e943aea149c62b2bafe2356d7 100644
--- a/spec/lib/gitlab/group_hierarchy_spec.rb
+++ b/spec/lib/gitlab/group_hierarchy_spec.rb
@@ -17,6 +17,12 @@ describe Gitlab::GroupHierarchy, :postgresql do
     it 'includes all of the ancestors' do
       expect(relation).to include(parent, child1)
     end
+
+    it 'uses ancestors_base #initialize argument' do
+      relation = described_class.new(Group.where(id: child2.id), Group.none).base_and_ancestors
+
+      expect(relation).to include(parent, child1, child2)
+    end
   end
 
   describe '#base_and_descendants' do
@@ -31,6 +37,12 @@ describe Gitlab::GroupHierarchy, :postgresql do
     it 'includes all the descendants' do
       expect(relation).to include(child1, child2)
     end
+
+    it 'uses descendants_base #initialize argument' do
+      relation = described_class.new(Group.none, Group.where(id: parent.id)).base_and_descendants
+
+      expect(relation).to include(parent, child1, child2)
+    end
   end
 
   describe '#all_groups' do
@@ -49,5 +61,17 @@ describe Gitlab::GroupHierarchy, :postgresql do
     it 'includes the descendants' do
       expect(relation).to include(child2)
     end
+
+    it 'uses ancestors_base #initialize argument for ancestors' do
+      relation = described_class.new(Group.where(id: child1.id), Group.where(id: Group.maximum(:id).succ)).all_groups
+
+      expect(relation).to include(parent)
+    end
+
+    it 'uses descendants_base #initialize argument for descendants' do
+      relation = described_class.new(Group.where(id: Group.maximum(:id).succ), Group.where(id: child1.id)).all_groups
+
+      expect(relation).to include(child2)
+    end
   end
 end
diff --git a/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb b/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb
index 61c10d47434bf7c6a74b51279bf136474f23e6a7..b333e162909ccab1fd045aef75aa408510b64139 100644
--- a/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb
+++ b/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb
@@ -97,30 +97,40 @@ describe Gitlab::HealthChecks::FsShardsCheck do
           }.with_indifferent_access
         end
 
-        it { is_expected.to all(have_attributes(labels: { shard: :default })) }
+        # Unsolved intermittent failure in CI https://gitlab.com/gitlab-org/gitlab-ce/issues/31128
+        around(:each) do |example| # rubocop:disable RSpec/AroundBlock
+          times_to_try = ENV['CI'] ? 4 : 1
+          example.run_with_retry retry: times_to_try
+        end
 
-        it { is_expected.to include(an_object_having_attributes(name: :filesystem_accessible, value: 0)) }
-        it { is_expected.to include(an_object_having_attributes(name: :filesystem_readable, value: 0)) }
-        it { is_expected.to include(an_object_having_attributes(name: :filesystem_writable, value: 0)) }
+        it 'provides metrics' do
+          expect(subject).to all(have_attributes(labels: { shard: :default }))
+          expect(subject).to include(an_object_having_attributes(name: :filesystem_accessible, value: 0))
+          expect(subject).to include(an_object_having_attributes(name: :filesystem_readable, value: 0))
+          expect(subject).to include(an_object_having_attributes(name: :filesystem_writable, value: 0))
 
-        it { is_expected.to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0)) }
-        it { is_expected.to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0)) }
-        it { is_expected.to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0)) }
+          expect(subject).to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0))
+          expect(subject).to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0))
+          expect(subject).to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0))
+        end
       end
 
       context 'storage points to directory that has both read and write rights' do
         before do
           FileUtils.chmod_R(0755, tmp_dir)
         end
-        it { is_expected.to all(have_attributes(labels: { shard: :default })) }
 
-        it { is_expected.to include(an_object_having_attributes(name: :filesystem_accessible, value: 1)) }
-        it { is_expected.to include(an_object_having_attributes(name: :filesystem_readable, value: 1)) }
-        it { is_expected.to include(an_object_having_attributes(name: :filesystem_writable, value: 1)) }
+        it 'provides metrics' do
+          expect(subject).to all(have_attributes(labels: { shard: :default }))
 
-        it { is_expected.to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0)) }
-        it { is_expected.to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0)) }
-        it { is_expected.to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0)) }
+          expect(subject).to include(an_object_having_attributes(name: :filesystem_accessible, value: 1))
+          expect(subject).to include(an_object_having_attributes(name: :filesystem_readable, value: 1))
+          expect(subject).to include(an_object_having_attributes(name: :filesystem_writable, value: 1))
+
+          expect(subject).to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0))
+          expect(subject).to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0))
+          expect(subject).to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0))
+        end
       end
     end
   end
diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb
index fdc5b484ef187eeffa3649382ed6b964a0d1131e..07687b470c5ba07ed4cf8159679736c5e3f45130 100644
--- a/spec/lib/gitlab/highlight_spec.rb
+++ b/spec/lib/gitlab/highlight_spec.rb
@@ -51,8 +51,8 @@ describe Gitlab::Highlight, lib: true do
     end
 
     it 'links dependencies via DependencyLinker' do
-      expect(Gitlab::DependencyLinker).to receive(:link).
-        with('file.name', 'Contents', anything).and_call_original
+      expect(Gitlab::DependencyLinker).to receive(:link)
+        .with('file.name', 'Contents', anything).and_call_original
 
       described_class.highlight('file.name', 'Contents')
     end
diff --git a/spec/lib/gitlab/identifier_spec.rb b/spec/lib/gitlab/identifier_spec.rb
index bb758a8a2029edd712c811e2ae5830742517eb95..29912da2e252038fcbd2bd33e2f19c4b8e38f631 100644
--- a/spec/lib/gitlab/identifier_spec.rb
+++ b/spec/lib/gitlab/identifier_spec.rb
@@ -12,8 +12,8 @@ describe Gitlab::Identifier do
   describe '#identify' do
     context 'without an identifier' do
       it 'identifies the user using a commit' do
-        expect(identifier).to receive(:identify_using_commit).
-          with(project, '123')
+        expect(identifier).to receive(:identify_using_commit)
+          .with(project, '123')
 
         identifier.identify('', project, '123')
       end
@@ -21,8 +21,8 @@ describe Gitlab::Identifier do
 
     context 'with a user identifier' do
       it 'identifies the user using a user ID' do
-        expect(identifier).to receive(:identify_using_user).
-          with("user-#{user.id}")
+        expect(identifier).to receive(:identify_using_user)
+          .with("user-#{user.id}")
 
         identifier.identify("user-#{user.id}", project, '123')
       end
@@ -30,8 +30,8 @@ describe Gitlab::Identifier do
 
     context 'with an SSH key identifier' do
       it 'identifies the user using an SSH key ID' do
-        expect(identifier).to receive(:identify_using_ssh_key).
-          with("key-#{key.id}")
+        expect(identifier).to receive(:identify_using_ssh_key)
+          .with("key-#{key.id}")
 
         identifier.identify("key-#{key.id}", project, '123')
       end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 412eb33b35b0b1a3733dec067f118c75d190e26d..a5f09f1856e170e8df341cbb1146ef607c0f2e0d 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -88,6 +88,9 @@ merge_requests:
 - head_pipeline
 merge_request_diff:
 - merge_request
+- merge_request_diff_files
+merge_request_diff_files:
+- merge_request_diff
 pipelines:
 - project
 - user
diff --git a/spec/lib/gitlab/import_export/fork_spec.rb b/spec/lib/gitlab/import_export/fork_spec.rb
index 42f3fc59f041ddee07c60c1793820258c22bf501..7079678153221e31dbb54f2908f2418e529520f0 100644
--- a/spec/lib/gitlab/import_export/fork_spec.rb
+++ b/spec/lib/gitlab/import_export/fork_spec.rb
@@ -44,6 +44,8 @@ describe 'forked project import', services: true do
   end
 
   it 'can access the MR' do
-    expect(project.merge_requests.first.ensure_ref_fetched.first).to include('refs/merge-requests/1/head')
+    project.merge_requests.first.ensure_ref_fetched
+
+    expect(project.repository.ref_exists?('refs/merge-requests/1/head')).to be_truthy
   end
 end
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index e3599d6fe594d5e7841c37e03f576ecf8f2c9fdf..98c117b4cd8323a8980ded7cf2df9fee8e8e0e7d 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -2821,9 +2821,11 @@
             "committer_email": "dmitriy.zaporozhets@gmail.com"
           }
         ],
-        "utf8_st_diffs": [
+        "merge_request_diff_files": [
           {
-            "diff": "Binary files a/.DS_Store and /dev/null differ\n",
+            "merge_request_diff_id": 27,
+            "relative_order": 0,
+            "utf8_diff": "Binary files a/.DS_Store and /dev/null differ\n",
             "new_path": ".DS_Store",
             "old_path": ".DS_Store",
             "a_mode": "100644",
@@ -2834,7 +2836,9 @@
             "too_large": false
           },
           {
-            "diff": "--- a/.gitignore\n+++ b/.gitignore\n@@ -17,3 +17,4 @@ rerun.txt\n pickle-email-*.html\n .project\n config/initializers/secret_token.rb\n+.DS_Store\n",
+            "merge_request_diff_id": 27,
+            "relative_order": 1,
+            "utf8_diff": "--- a/.gitignore\n+++ b/.gitignore\n@@ -17,3 +17,4 @@ rerun.txt\n pickle-email-*.html\n .project\n config/initializers/secret_token.rb\n+.DS_Store\n",
             "new_path": ".gitignore",
             "old_path": ".gitignore",
             "a_mode": "100644",
@@ -2845,7 +2849,9 @@
             "too_large": false
           },
           {
-            "diff": "--- a/.gitmodules\n+++ b/.gitmodules\n@@ -1,3 +1,9 @@\n [submodule \"six\"]\n \tpath = six\n \turl = git://github.com/randx/six.git\n+[submodule \"gitlab-shell\"]\n+\tpath = gitlab-shell\n+\turl = https://github.com/gitlabhq/gitlab-shell.git\n+[submodule \"gitlab-grack\"]\n+\tpath = gitlab-grack\n+\turl = https://gitlab.com/gitlab-org/gitlab-grack.git\n",
+            "merge_request_diff_id": 27,
+            "relative_order": 2,
+            "utf8_diff": "--- a/.gitmodules\n+++ b/.gitmodules\n@@ -1,3 +1,9 @@\n [submodule \"six\"]\n \tpath = six\n \turl = git://github.com/randx/six.git\n+[submodule \"gitlab-shell\"]\n+\tpath = gitlab-shell\n+\turl = https://github.com/gitlabhq/gitlab-shell.git\n+[submodule \"gitlab-grack\"]\n+\tpath = gitlab-grack\n+\turl = https://gitlab.com/gitlab-org/gitlab-grack.git\n",
             "new_path": ".gitmodules",
             "old_path": ".gitmodules",
             "a_mode": "100644",
@@ -2856,7 +2862,9 @@
             "too_large": false
           },
           {
-            "diff": "Binary files a/files/.DS_Store and /dev/null differ\n",
+            "merge_request_diff_id": 27,
+            "relative_order": 3,
+            "utf8_diff": "Binary files a/files/.DS_Store and /dev/null differ\n",
             "new_path": "files/.DS_Store",
             "old_path": "files/.DS_Store",
             "a_mode": "100644",
@@ -2867,7 +2875,9 @@
             "too_large": false
           },
           {
-            "diff": "--- /dev/null\n+++ b/files/ruby/feature.rb\n@@ -0,0 +1,4 @@\n+# This file was changed in feature branch\n+# We put different code here to make merge conflict\n+class Conflict\n+end\n",
+            "merge_request_diff_id": 27,
+            "relative_order": 4,
+            "utf8_diff": "--- /dev/null\n+++ b/files/ruby/feature.rb\n@@ -0,0 +1,4 @@\n+# This file was changed in feature branch\n+# We put different code here to make merge conflict\n+class Conflict\n+end\n",
             "new_path": "files/ruby/feature.rb",
             "old_path": "files/ruby/feature.rb",
             "a_mode": "0",
@@ -2878,7 +2888,9 @@
             "too_large": false
           },
           {
-            "diff": "--- a/files/ruby/popen.rb\n+++ b/files/ruby/popen.rb\n@@ -6,12 +6,18 @@ module Popen\n \n   def popen(cmd, path=nil)\n     unless cmd.is_a?(Array)\n-      raise \"System commands must be given as an array of strings\"\n+      raise RuntimeError, \"System commands must be given as an array of strings\"\n     end\n \n     path ||= Dir.pwd\n-    vars = { \"PWD\" =\u003e path }\n-    options = { chdir: path }\n+\n+    vars = {\n+      \"PWD\" =\u003e path\n+    }\n+\n+    options = {\n+      chdir: path\n+    }\n \n     unless File.directory?(path)\n       FileUtils.mkdir_p(path)\n@@ -19,6 +25,7 @@ module Popen\n \n     @cmd_output = \"\"\n     @cmd_status = 0\n+\n     Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|\n       @cmd_output \u003c\u003c stdout.read\n       @cmd_output \u003c\u003c stderr.read\n",
+            "merge_request_diff_id": 27,
+            "relative_order": 5,
+            "utf8_diff": "--- a/files/ruby/popen.rb\n+++ b/files/ruby/popen.rb\n@@ -6,12 +6,18 @@ module Popen\n \n   def popen(cmd, path=nil)\n     unless cmd.is_a?(Array)\n-      raise \"System commands must be given as an array of strings\"\n+      raise RuntimeError, \"System commands must be given as an array of strings\"\n     end\n \n     path ||= Dir.pwd\n-    vars = { \"PWD\" =\u003e path }\n-    options = { chdir: path }\n+\n+    vars = {\n+      \"PWD\" =\u003e path\n+    }\n+\n+    options = {\n+      chdir: path\n+    }\n \n     unless File.directory?(path)\n       FileUtils.mkdir_p(path)\n@@ -19,6 +25,7 @@ module Popen\n \n     @cmd_output = \"\"\n     @cmd_status = 0\n+\n     Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|\n       @cmd_output \u003c\u003c stdout.read\n       @cmd_output \u003c\u003c stderr.read\n",
             "new_path": "files/ruby/popen.rb",
             "old_path": "files/ruby/popen.rb",
             "a_mode": "100644",
@@ -2889,7 +2901,9 @@
             "too_large": false
           },
           {
-            "diff": "--- a/files/ruby/regex.rb\n+++ b/files/ruby/regex.rb\n@@ -19,14 +19,12 @@ module Gitlab\n     end\n \n     def archive_formats_regex\n-      #|zip|tar|    tar.gz    |         tar.bz2         |\n-      /(zip|tar|tar\\.gz|tgz|gz|tar\\.bz2|tbz|tbz2|tb2|bz2)/\n+      /(zip|tar|7z|tar\\.gz|tgz|gz|tar\\.bz2|tbz|tbz2|tb2|bz2)/\n     end\n \n     def git_reference_regex\n       # Valid git ref regex, see:\n       # https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html\n-\n       %r{\n         (?!\n            (?# doesn't begins with)\n",
+            "merge_request_diff_id": 27,
+            "relative_order": 6,
+            "utf8_diff": "--- a/files/ruby/regex.rb\n+++ b/files/ruby/regex.rb\n@@ -19,14 +19,12 @@ module Gitlab\n     end\n \n     def archive_formats_regex\n-      #|zip|tar|    tar.gz    |         tar.bz2         |\n-      /(zip|tar|tar\\.gz|tgz|gz|tar\\.bz2|tbz|tbz2|tb2|bz2)/\n+      /(zip|tar|7z|tar\\.gz|tgz|gz|tar\\.bz2|tbz|tbz2|tb2|bz2)/\n     end\n \n     def git_reference_regex\n       # Valid git ref regex, see:\n       # https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html\n-\n       %r{\n         (?!\n            (?# doesn't begins with)\n",
             "new_path": "files/ruby/regex.rb",
             "old_path": "files/ruby/regex.rb",
             "a_mode": "100644",
@@ -2900,7 +2914,9 @@
             "too_large": false
           },
           {
-            "diff": "--- /dev/null\n+++ b/gitlab-grack\n@@ -0,0 +1 @@\n+Subproject commit 645f6c4c82fd3f5e06f67134450a570b795e55a6\n",
+            "merge_request_diff_id": 27,
+            "relative_order": 7,
+            "utf8_diff": "--- /dev/null\n+++ b/gitlab-grack\n@@ -0,0 +1 @@\n+Subproject commit 645f6c4c82fd3f5e06f67134450a570b795e55a6\n",
             "new_path": "gitlab-grack",
             "old_path": "gitlab-grack",
             "a_mode": "0",
@@ -2911,7 +2927,9 @@
             "too_large": false
           },
           {
-            "diff": "--- /dev/null\n+++ b/gitlab-shell\n@@ -0,0 +1 @@\n+Subproject commit 79bceae69cb5750d6567b223597999bfa91cb3b9\n",
+            "merge_request_diff_id": 27,
+            "relative_order": 8,
+            "utf8_diff": "--- /dev/null\n+++ b/gitlab-shell\n@@ -0,0 +1 @@\n+Subproject commit 79bceae69cb5750d6567b223597999bfa91cb3b9\n",
             "new_path": "gitlab-shell",
             "old_path": "gitlab-shell",
             "a_mode": "0",
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 14338515892f3af832d690a20f8f79faca4614bd..c11b15a811b962ce0ccd553ab4048a2140bc9b41 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -86,8 +86,13 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
 
       it 'has the correct data for merge request st_diffs' do
         # makes sure we are renaming the custom method +utf8_st_diffs+ into +st_diffs+
+        # one MergeRequestDiff uses the new format, where st_diffs is expected to be nil
 
-        expect(MergeRequestDiff.where.not(st_diffs: nil).count).to eq(9)
+        expect(MergeRequestDiff.where.not(st_diffs: nil).count).to eq(8)
+      end
+
+      it 'has the correct data for merge request diff files' do
+        expect(MergeRequestDiffFile.where.not(diff: nil).count).to eq(9)
       end
 
       it 'has the correct time for merge request st_commits' do
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 5aeb29b7fecb72676fa1d4128e38b2208f4bba29..e52f79513f16672fa6e7444e146fd54c2b8c8d0c 100644
--- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
@@ -83,6 +83,10 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
         expect(saved_project_json['merge_requests'].first['merge_request_diff']['utf8_st_diffs']).not_to be_nil
       end
 
+      it 'has merge request diff files' do
+        expect(saved_project_json['merge_requests'].first['merge_request_diff']['merge_request_diff_files']).not_to be_empty
+      end
+
       it 'has merge requests comments' do
         expect(saved_project_json['merge_requests'].first['notes']).not_to be_empty
       end
@@ -145,6 +149,12 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
         expect(project_tree_saver.save).to be true
       end
 
+      it 'does not complain about non UTF-8 characters in MR diff files' do
+        ActiveRecord::Base.connection.execute("UPDATE merge_request_diff_files SET diff = '---\n- :diff: !binary |-\n    LS0tIC9kZXYvbnVsbAorKysgYi9pbWFnZXMvbnVjb3IucGRmCkBAIC0wLDAg\n    KzEsMTY3OSBAQAorJVBERi0xLjUNJeLjz9MNCisxIDAgb2JqDTw8L01ldGFk\n    YXR'")
+
+        expect(project_tree_saver.save).to be true
+      end
+
       context 'group members' do
         let(:user2) { create(:user, email: 'group@member.com') }
         let(:member_emails) do
diff --git a/spec/lib/gitlab/import_export/repo_restorer_spec.rb b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
index 168a59e51391ae7e18e55989f121f87ff5cc61ca..30b6a0d88458c806069a30b0979f69ce09c56e41 100644
--- a/spec/lib/gitlab/import_export/repo_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
@@ -34,7 +34,7 @@ describe Gitlab::ImportExport::RepoRestorer, services: true do
     it 'has the webhooks' do
       restorer.restore
 
-      expect(Gitlab::Git::Hook.new('post-receive', project.repository.path_to_repo)).to exist
+      expect(Gitlab::Git::Hook.new('post-receive', project)).to exist
     end
   end
 end
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 50ff6ecc1e08b19d3cd2010a9579e0a5fb81cc09..697ddf52af9d0e1b036f9db1d2bdcfc6a04aaa38 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -172,6 +172,17 @@ MergeRequestDiff:
 - real_size
 - head_commit_sha
 - start_commit_sha
+MergeRequestDiffFile:
+- merge_request_diff_id
+- relative_order
+- new_file
+- renamed_file
+- deleted_file
+- new_path
+- old_path
+- a_mode
+- b_mode
+- too_large
 Ci::Pipeline:
 - id
 - project_id
@@ -372,6 +383,7 @@ Project:
 - printing_merge_request_link_enabled
 - build_allow_git_fetch
 - last_repository_updated_at
+- ci_config_path
 Author:
 - name
 ProjectFeature:
diff --git a/spec/lib/gitlab/job_waiter_spec.rb b/spec/lib/gitlab/job_waiter_spec.rb
index 780f5b1f8d7a4abaa49c42ac33f20ee5125e138e..6186cec268993200b7cd0b4334fbb503fd530a69 100644
--- a/spec/lib/gitlab/job_waiter_spec.rb
+++ b/spec/lib/gitlab/job_waiter_spec.rb
@@ -4,8 +4,8 @@ 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(Gitlab::SidekiqStatus).to receive(:all_completed?).with(%w(a))
+        .and_return(true)
 
       expect(waiter).not_to receive(:sleep)
 
@@ -13,9 +13,9 @@ describe Gitlab::JobWaiter do
     end
 
     it 'sleeps between checking the job statuses' do
-      expect(Gitlab::SidekiqStatus).to receive(:all_completed?).
-        with(%w(a)).
-        and_return(false, true)
+      expect(Gitlab::SidekiqStatus).to receive(:all_completed?)
+        .with(%w(a))
+        .and_return(false, true)
 
       expect(waiter).to receive(:sleep).with(described_class::INTERVAL)
 
diff --git a/spec/lib/gitlab/ldap/access_spec.rb b/spec/lib/gitlab/ldap/access_spec.rb
index 9dd997aa7dc5cb60065d9e32c852f8c487925ee9..756fcb0fcafe8fac4c6de808e05446ceada29ccd 100644
--- a/spec/lib/gitlab/ldap/access_spec.rb
+++ b/spec/lib/gitlab/ldap/access_spec.rb
@@ -4,6 +4,16 @@ describe Gitlab::LDAP::Access, lib: true do
   let(:access) { Gitlab::LDAP::Access.new user }
   let(:user) { create(:omniauth_user) }
 
+  describe '.allowed?' do
+    it 'updates the users `last_credential_check_at' do
+      expect(access).to receive(:allowed?) { true }
+      expect(described_class).to receive(:open).and_yield(access)
+
+      expect { described_class.allowed?(user) }
+        .to change { user.last_credential_check_at }
+    end
+  end
+
   describe '#allowed?' do
     subject { access.allowed? }
 
diff --git a/spec/lib/gitlab/ldap/authentication_spec.rb b/spec/lib/gitlab/ldap/authentication_spec.rb
index b8f3290e84c9cd4013f25900e91130f2de31b52b..f689b47fec4c2c3f9bb91bc5085d4a2f589d1aaf 100644
--- a/spec/lib/gitlab/ldap/authentication_spec.rb
+++ b/spec/lib/gitlab/ldap/authentication_spec.rb
@@ -16,8 +16,8 @@ describe Gitlab::LDAP::Authentication, lib: true do
 
       # try only to fake the LDAP call
       adapter = double('adapter', dn: dn).as_null_object
-      allow_any_instance_of(described_class).
-        to receive(:adapter).and_return(adapter)
+      allow_any_instance_of(described_class)
+        .to receive(:adapter).and_return(adapter)
 
       expect(described_class.login(login, password)).to be_truthy
     end
@@ -25,8 +25,8 @@ describe Gitlab::LDAP::Authentication, lib: true do
     it "is false if the user does not exist" do
       # try only to fake the LDAP call
       adapter = double('adapter', dn: dn).as_null_object
-      allow_any_instance_of(described_class).
-        to receive(:adapter).and_return(adapter)
+      allow_any_instance_of(described_class)
+        .to receive(:adapter).and_return(adapter)
 
       expect(described_class.login(login, password)).to be_falsey
     end
@@ -36,8 +36,8 @@ describe Gitlab::LDAP::Authentication, lib: true do
 
       # try only to fake the LDAP call
       adapter = double('adapter', bind_as: nil).as_null_object
-      allow_any_instance_of(described_class).
-        to receive(:adapter).and_return(adapter)
+      allow_any_instance_of(described_class)
+        .to receive(:adapter).and_return(adapter)
 
       expect(described_class.login(login, password)).to be_falsey
     end
diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb
index f0a1dd22fee775c2ed18be5fba5e8b78b34df97c..b796d8bf076036bde5b8fd72c445054cde3bdd62 100644
--- a/spec/lib/gitlab/ldap/user_spec.rb
+++ b/spec/lib/gitlab/ldap/user_spec.rb
@@ -167,8 +167,8 @@ describe Gitlab::LDAP::User, lib: true do
 
   describe 'blocking' do
     def configure_block(value)
-      allow_any_instance_of(Gitlab::LDAP::Config).
-        to receive(:block_auto_created_users).and_return(value)
+      allow_any_instance_of(Gitlab::LDAP::Config)
+        .to receive(:block_auto_created_users).and_return(value)
     end
 
     context 'signup' do
diff --git a/spec/lib/gitlab/metrics/connection_rack_middleware_spec.rb b/spec/lib/gitlab/metrics/connection_rack_middleware_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..94251af305f6f940893ffbd97d9f393847cb0b0e
--- /dev/null
+++ b/spec/lib/gitlab/metrics/connection_rack_middleware_spec.rb
@@ -0,0 +1,88 @@
+require 'spec_helper'
+
+describe Gitlab::Metrics::ConnectionRackMiddleware do
+  let(:app) { double('app') }
+  subject { described_class.new(app) }
+
+  around do |example|
+    Timecop.freeze { example.run }
+  end
+
+  describe '#call' do
+    let(:status) { 100 }
+    let(:env) { { 'REQUEST_METHOD' => 'GET' } }
+    let(:stack_result) { [status, {}, 'body'] }
+
+    before do
+      allow(app).to receive(:call).and_return(stack_result)
+    end
+
+    context '@app.call succeeds with 200' do
+      before do
+        allow(app).to receive(:call).and_return([200, nil, nil])
+      end
+
+      it 'increments response count with status label' do
+        expect(described_class).to receive_message_chain(:rack_response_count, :increment).with(include(status: 200, method: 'get'))
+
+        subject.call(env)
+      end
+
+      it 'increments requests count' do
+        expect(described_class).to receive_message_chain(:rack_request_count, :increment).with(method: 'get')
+
+        subject.call(env)
+      end
+
+      it 'measures execution time' do
+        execution_time = 10
+        allow(app).to receive(:call) do |*args|
+          Timecop.freeze(execution_time.seconds)
+        end
+
+        expect(described_class).to receive_message_chain(:rack_execution_time, :observe).with({}, execution_time)
+
+        subject.call(env)
+      end
+    end
+
+    context '@app.call throws exception' do
+      let(:rack_response_count) { double('rack_response_count') }
+
+      before do
+        allow(app).to receive(:call).and_raise(StandardError)
+        allow(described_class).to receive(:rack_response_count).and_return(rack_response_count)
+      end
+
+      it 'increments exceptions count' do
+        expect(described_class).to receive_message_chain(:rack_uncaught_errors_count, :increment)
+
+        expect { subject.call(env) }.to raise_error(StandardError)
+      end
+
+      it 'increments requests count' do
+        expect(described_class).to receive_message_chain(:rack_request_count, :increment).with(method: 'get')
+
+        expect { subject.call(env) }.to raise_error(StandardError)
+      end
+
+      it "does't increment response count" do
+        expect(described_class.rack_response_count).not_to receive(:increment)
+
+        expect { subject.call(env) }.to raise_error(StandardError)
+      end
+
+      it 'measures execution time' do
+        execution_time = 10
+        allow(app).to receive(:call) do |*args|
+          Timecop.freeze(execution_time.seconds)
+          raise StandardError
+        end
+
+        expect(described_class).to receive_message_chain(:rack_execution_time, :observe).with({}, execution_time)
+
+        expect { subject.call(env) }.to raise_error(StandardError)
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/metrics/sampler_spec.rb b/spec/lib/gitlab/metrics/influx_sampler_spec.rb
similarity index 66%
rename from spec/lib/gitlab/metrics/sampler_spec.rb
rename to spec/lib/gitlab/metrics/influx_sampler_spec.rb
index 1ab923b58cf6881b901a5c0445b1231ab00648b9..0bc68d642761b559c8b30f13d64e24f083ca5f2f 100644
--- a/spec/lib/gitlab/metrics/sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/influx_sampler_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::Metrics::Sampler do
+describe Gitlab::Metrics::InfluxSampler do
   let(:sampler) { described_class.new(5) }
 
   after do
@@ -8,10 +8,10 @@ describe Gitlab::Metrics::Sampler do
   end
 
   describe '#start' do
-    it 'gathers a sample at a given interval' do
-      expect(sampler).to receive(:sleep).with(a_kind_of(Numeric))
-      expect(sampler).to receive(:sample)
-      expect(sampler).to receive(:loop).and_yield
+    it 'runs once and gathers a sample at a given interval' do
+      expect(sampler).to receive(:sleep).with(a_kind_of(Numeric)).twice
+      expect(sampler).to receive(:sample).once
+      expect(sampler).to receive(:running).and_return(false, true, false)
 
       sampler.start.join
     end
@@ -38,8 +38,8 @@ describe Gitlab::Metrics::Sampler do
 
   describe '#flush' do
     it 'schedules the metrics using Sidekiq' do
-      expect(Gitlab::Metrics).to receive(:submit_metrics).
-        with([an_instance_of(Hash)])
+      expect(Gitlab::Metrics).to receive(:submit_metrics)
+        .with([an_instance_of(Hash)])
 
       sampler.sample_memory_usage
       sampler.flush
@@ -48,12 +48,12 @@ describe Gitlab::Metrics::Sampler do
 
   describe '#sample_memory_usage' do
     it 'adds a metric containing the memory usage' do
-      expect(Gitlab::Metrics::System).to receive(:memory_usage).
-        and_return(9000)
+      expect(Gitlab::Metrics::System).to receive(:memory_usage)
+        .and_return(9000)
 
-      expect(sampler).to receive(:add_metric).
-        with(/memory_usage/, value: 9000).
-        and_call_original
+      expect(sampler).to receive(:add_metric)
+        .with(/memory_usage/, value: 9000)
+        .and_call_original
 
       sampler.sample_memory_usage
     end
@@ -61,12 +61,12 @@ describe Gitlab::Metrics::Sampler do
 
   describe '#sample_file_descriptors' do
     it 'adds a metric containing the amount of open file descriptors' do
-      expect(Gitlab::Metrics::System).to receive(:file_descriptor_count).
-        and_return(4)
+      expect(Gitlab::Metrics::System).to receive(:file_descriptor_count)
+        .and_return(4)
 
-      expect(sampler).to receive(:add_metric).
-        with(/file_descriptors/, value: 4).
-        and_call_original
+      expect(sampler).to receive(:add_metric)
+        .with(/file_descriptors/, value: 4)
+        .and_call_original
 
       sampler.sample_file_descriptors
     end
@@ -75,10 +75,10 @@ describe Gitlab::Metrics::Sampler do
   if Gitlab::Metrics.mri?
     describe '#sample_objects' do
       it 'adds a metric containing the amount of allocated objects' do
-        expect(sampler).to receive(:add_metric).
-          with(/object_counts/, an_instance_of(Hash), an_instance_of(Hash)).
-          at_least(:once).
-          and_call_original
+        expect(sampler).to receive(:add_metric)
+          .with(/object_counts/, an_instance_of(Hash), an_instance_of(Hash))
+          .at_least(:once)
+          .and_call_original
 
         sampler.sample_objects
       end
@@ -86,8 +86,8 @@ describe Gitlab::Metrics::Sampler do
       it 'ignores classes without a name' do
         expect(Allocations).to receive(:to_hash).and_return({ Class.new => 4 })
 
-        expect(sampler).not_to receive(:add_metric).
-          with('object_counts', an_instance_of(Hash), type: nil)
+        expect(sampler).not_to receive(:add_metric)
+          .with('object_counts', an_instance_of(Hash), type: nil)
 
         sampler.sample_objects
       end
@@ -98,9 +98,9 @@ describe Gitlab::Metrics::Sampler do
     it 'adds a metric containing garbage collection statistics' do
       expect(GC::Profiler).to receive(:total_time).and_return(0.24)
 
-      expect(sampler).to receive(:add_metric).
-        with(/gc_statistics/, an_instance_of(Hash)).
-        and_call_original
+      expect(sampler).to receive(:add_metric)
+        .with(/gc_statistics/, an_instance_of(Hash))
+        .and_call_original
 
       sampler.sample_gc
     end
@@ -110,9 +110,9 @@ describe Gitlab::Metrics::Sampler do
     it 'prefixes the series name for a Rails process' do
       expect(sampler).to receive(:sidekiq?).and_return(false)
 
-      expect(Gitlab::Metrics::Metric).to receive(:new).
-        with('rails_cats', { value: 10 }, {}).
-        and_call_original
+      expect(Gitlab::Metrics::Metric).to receive(:new)
+        .with('rails_cats', { value: 10 }, {})
+        .and_call_original
 
       sampler.add_metric('cats', value: 10)
     end
@@ -120,9 +120,9 @@ describe Gitlab::Metrics::Sampler do
     it 'prefixes the series name for a Sidekiq process' do
       expect(sampler).to receive(:sidekiq?).and_return(true)
 
-      expect(Gitlab::Metrics::Metric).to receive(:new).
-        with('sidekiq_cats', { value: 10 }, {}).
-        and_call_original
+      expect(Gitlab::Metrics::Metric).to receive(:new)
+        .with('sidekiq_cats', { value: 10 }, {})
+        .and_call_original
 
       sampler.add_metric('cats', value: 10)
     end
diff --git a/spec/lib/gitlab/metrics/instrumentation_spec.rb b/spec/lib/gitlab/metrics/instrumentation_spec.rb
index a986cb520fb0c30d032caaa8c5cb13412d8aaca0..4b19ee19103661583725a445f0913952807e2002 100644
--- a/spec/lib/gitlab/metrics/instrumentation_spec.rb
+++ b/spec/lib/gitlab/metrics/instrumentation_spec.rb
@@ -78,11 +78,11 @@ describe Gitlab::Metrics::Instrumentation do
       end
 
       it 'tracks the call duration upon calling the method' do
-        allow(Gitlab::Metrics).to receive(:method_call_threshold).
-          and_return(0)
+        allow(Gitlab::Metrics).to receive(:method_call_threshold)
+          .and_return(0)
 
-        allow(described_class).to receive(:transaction).
-          and_return(transaction)
+        allow(described_class).to receive(:transaction)
+          .and_return(transaction)
 
         expect_any_instance_of(Gitlab::Metrics::MethodCall).to receive(:measure)
 
@@ -90,8 +90,8 @@ describe Gitlab::Metrics::Instrumentation do
       end
 
       it 'does not track method calls below a given duration threshold' do
-        allow(Gitlab::Metrics).to receive(:method_call_threshold).
-          and_return(100)
+        allow(Gitlab::Metrics).to receive(:method_call_threshold)
+          .and_return(100)
 
         expect(transaction).not_to receive(:add_metric)
 
@@ -137,8 +137,8 @@ describe Gitlab::Metrics::Instrumentation do
       before do
         allow(Gitlab::Metrics).to receive(:enabled?).and_return(true)
 
-        described_class.
-          instrument_instance_method(@dummy, :bar)
+        described_class
+          .instrument_instance_method(@dummy, :bar)
       end
 
       it 'instruments instances of the Class' do
@@ -156,11 +156,11 @@ describe Gitlab::Metrics::Instrumentation do
       end
 
       it 'tracks the call duration upon calling the method' do
-        allow(Gitlab::Metrics).to receive(:method_call_threshold).
-          and_return(0)
+        allow(Gitlab::Metrics).to receive(:method_call_threshold)
+          .and_return(0)
 
-        allow(described_class).to receive(:transaction).
-          and_return(transaction)
+        allow(described_class).to receive(:transaction)
+          .and_return(transaction)
 
         expect_any_instance_of(Gitlab::Metrics::MethodCall).to receive(:measure)
 
@@ -168,8 +168,8 @@ describe Gitlab::Metrics::Instrumentation do
       end
 
       it 'does not track method calls below a given duration threshold' do
-        allow(Gitlab::Metrics).to receive(:method_call_threshold).
-          and_return(100)
+        allow(Gitlab::Metrics).to receive(:method_call_threshold)
+          .and_return(100)
 
         expect(transaction).not_to receive(:add_metric)
 
@@ -183,8 +183,8 @@ describe Gitlab::Metrics::Instrumentation do
       end
 
       it 'does not instrument the method' do
-        described_class.
-          instrument_instance_method(@dummy, :bar)
+        described_class
+          .instrument_instance_method(@dummy, :bar)
 
         expect(described_class.instrumented?(@dummy)).to eq(false)
       end
diff --git a/spec/lib/gitlab/metrics/rack_middleware_spec.rb b/spec/lib/gitlab/metrics/rack_middleware_spec.rb
index fb470ea75684f567dd05e86106e7742f44272207..ec415f2bd8598c4418d1f8a39108a16109b4719a 100644
--- a/spec/lib/gitlab/metrics/rack_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/rack_middleware_spec.rb
@@ -26,8 +26,8 @@ describe Gitlab::Metrics::RackMiddleware do
 
       allow(app).to receive(:call).with(env)
 
-      expect(middleware).to receive(:tag_controller).
-        with(an_instance_of(Gitlab::Metrics::Transaction), env)
+      expect(middleware).to receive(:tag_controller)
+        .with(an_instance_of(Gitlab::Metrics::Transaction), env)
 
       middleware.call(env)
     end
@@ -40,8 +40,8 @@ describe Gitlab::Metrics::RackMiddleware do
 
       allow(app).to receive(:call).with(env)
 
-      expect(middleware).to receive(:tag_endpoint).
-        with(an_instance_of(Gitlab::Metrics::Transaction), env)
+      expect(middleware).to receive(:tag_endpoint)
+        .with(an_instance_of(Gitlab::Metrics::Transaction), env)
 
       middleware.call(env)
     end
@@ -49,8 +49,8 @@ describe Gitlab::Metrics::RackMiddleware do
     it 'tracks any raised exceptions' do
       expect(app).to receive(:call).with(env).and_raise(RuntimeError)
 
-      expect_any_instance_of(Gitlab::Metrics::Transaction).
-        to receive(:add_event).with(:rails_exception)
+      expect_any_instance_of(Gitlab::Metrics::Transaction)
+        .to receive(:add_event).with(:rails_exception)
 
       expect { middleware.call(env) }.to raise_error(RuntimeError)
     end
diff --git a/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb b/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb
index acaba785606e13e9b43348a3fa4247eb3ac56d91..b576d7173f5a040b5a0fdf6d2364ea96f7bbddb7 100644
--- a/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb
@@ -8,12 +8,12 @@ describe Gitlab::Metrics::SidekiqMiddleware do
     it 'tracks the transaction' do
       worker = double(:worker, class: double(:class, name: 'TestWorker'))
 
-      expect(Gitlab::Metrics::Transaction).to receive(:new).
-        with('TestWorker#perform').
-        and_call_original
+      expect(Gitlab::Metrics::Transaction).to receive(:new)
+        .with('TestWorker#perform')
+        .and_call_original
 
-      expect_any_instance_of(Gitlab::Metrics::Transaction).to receive(:set).
-        with(:sidekiq_queue_duration, instance_of(Float))
+      expect_any_instance_of(Gitlab::Metrics::Transaction).to receive(:set)
+        .with(:sidekiq_queue_duration, instance_of(Float))
 
       expect_any_instance_of(Gitlab::Metrics::Transaction).to receive(:finish)
 
@@ -23,12 +23,12 @@ describe Gitlab::Metrics::SidekiqMiddleware do
     it 'tracks the transaction (for messages without `enqueued_at`)' do
       worker = double(:worker, class: double(:class, name: 'TestWorker'))
 
-      expect(Gitlab::Metrics::Transaction).to receive(:new).
-        with('TestWorker#perform').
-        and_call_original
+      expect(Gitlab::Metrics::Transaction).to receive(:new)
+        .with('TestWorker#perform')
+        .and_call_original
 
-      expect_any_instance_of(Gitlab::Metrics::Transaction).to receive(:set).
-        with(:sidekiq_queue_duration, instance_of(Float))
+      expect_any_instance_of(Gitlab::Metrics::Transaction).to receive(:set)
+        .with(:sidekiq_queue_duration, instance_of(Float))
 
       expect_any_instance_of(Gitlab::Metrics::Transaction).to receive(:finish)
 
@@ -38,17 +38,17 @@ describe Gitlab::Metrics::SidekiqMiddleware do
     it 'tracks any raised exceptions' do
       worker = double(:worker, class: double(:class, name: 'TestWorker'))
 
-      expect_any_instance_of(Gitlab::Metrics::Transaction).
-        to receive(:run).and_raise(RuntimeError)
+      expect_any_instance_of(Gitlab::Metrics::Transaction)
+        .to receive(:run).and_raise(RuntimeError)
 
-      expect_any_instance_of(Gitlab::Metrics::Transaction).
-        to receive(:add_event).with(:sidekiq_exception)
+      expect_any_instance_of(Gitlab::Metrics::Transaction)
+        .to receive(:add_event).with(:sidekiq_exception)
 
-      expect_any_instance_of(Gitlab::Metrics::Transaction).
-        to receive(:finish)
+      expect_any_instance_of(Gitlab::Metrics::Transaction)
+        .to receive(:finish)
 
-      expect { middleware.call(worker, message, :test) }.
-        to raise_error(RuntimeError)
+      expect { middleware.call(worker, message, :test) }
+        .to raise_error(RuntimeError)
     end
   end
 end
diff --git a/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb
index 0695c5ce096fca07564bfe53d9397955797cdee6..e7b595405a81272cd8b8a96b530ad59a52bb283b 100644
--- a/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb
@@ -21,11 +21,11 @@ describe Gitlab::Metrics::Subscribers::ActionView do
       values = { duration: 2.1 }
       tags   = { view: 'app/views/x.html.haml' }
 
-      expect(transaction).to receive(:increment).
-        with(:view_duration, 2.1)
+      expect(transaction).to receive(:increment)
+        .with(:view_duration, 2.1)
 
-      expect(transaction).to receive(:add_metric).
-        with(described_class::SERIES, values, tags)
+      expect(transaction).to receive(:add_metric)
+        .with(described_class::SERIES, values, tags)
 
       subscriber.render_template(event)
     end
diff --git a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
index 49699ffe28f0a1875e8b1a2134f7ad23104ac7eb..ce6587e993f429b9e86e2c7c7c233d829cfde42c 100644
--- a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
@@ -12,8 +12,8 @@ describe Gitlab::Metrics::Subscribers::ActiveRecord do
   describe '#sql' do
     describe 'without a current transaction' do
       it 'simply returns' do
-        expect_any_instance_of(Gitlab::Metrics::Transaction).
-          not_to receive(:increment)
+        expect_any_instance_of(Gitlab::Metrics::Transaction)
+          .not_to receive(:increment)
 
         subscriber.sql(event)
       end
@@ -21,15 +21,15 @@ describe Gitlab::Metrics::Subscribers::ActiveRecord do
 
     describe 'with a current transaction' do
       it 'increments the :sql_duration value' do
-        expect(subscriber).to receive(:current_transaction).
-          at_least(:once).
-          and_return(transaction)
+        expect(subscriber).to receive(:current_transaction)
+          .at_least(:once)
+          .and_return(transaction)
 
-        expect(transaction).to receive(:increment).
-          with(:sql_duration, 0.2)
+        expect(transaction).to receive(:increment)
+          .with(:sql_duration, 0.2)
 
-        expect(transaction).to receive(:increment).
-          with(:sql_count, 1)
+        expect(transaction).to receive(:increment)
+          .with(:sql_count, 1)
 
         subscriber.sql(event)
       end
diff --git a/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb
index d986c6fac4380224837888008be0933918c1c686..f04dc8dcc02b58991b454721d831753c6a93ffd5 100644
--- a/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb
@@ -8,26 +8,26 @@ describe Gitlab::Metrics::Subscribers::RailsCache do
 
   describe '#cache_read' do
     it 'increments the cache_read duration' do
-      expect(subscriber).to receive(:increment).
-        with(:cache_read, event.duration)
+      expect(subscriber).to receive(:increment)
+        .with(:cache_read, event.duration)
 
       subscriber.cache_read(event)
     end
 
     context 'with a transaction' do
       before do
-        allow(subscriber).to receive(:current_transaction).
-          and_return(transaction)
+        allow(subscriber).to receive(:current_transaction)
+          .and_return(transaction)
       end
 
       context 'with hit event' do
         let(:event) { double(:event, duration: 15.2, payload: { hit: true }) }
 
         it 'increments the cache_read_hit count' do
-          expect(transaction).to receive(:increment).
-            with(:cache_read_hit_count, 1)
-          expect(transaction).to receive(:increment).
-            with(any_args).at_least(1) # Other calls
+          expect(transaction).to receive(:increment)
+            .with(:cache_read_hit_count, 1)
+          expect(transaction).to receive(:increment)
+            .with(any_args).at_least(1) # Other calls
 
           subscriber.cache_read(event)
         end
@@ -36,8 +36,8 @@ describe Gitlab::Metrics::Subscribers::RailsCache do
           let(:event) { double(:event, duration: 15.2, payload: { hit: true, super_operation: :fetch }) }
 
           it 'does not increment cache read miss' do
-            expect(transaction).not_to receive(:increment).
-              with(:cache_read_hit_count, 1)
+            expect(transaction).not_to receive(:increment)
+              .with(:cache_read_hit_count, 1)
 
             subscriber.cache_read(event)
           end
@@ -48,10 +48,10 @@ describe Gitlab::Metrics::Subscribers::RailsCache do
         let(:event) { double(:event, duration: 15.2, payload: { hit: false }) }
 
         it 'increments the cache_read_miss count' do
-          expect(transaction).to receive(:increment).
-            with(:cache_read_miss_count, 1)
-          expect(transaction).to receive(:increment).
-            with(any_args).at_least(1) # Other calls
+          expect(transaction).to receive(:increment)
+            .with(:cache_read_miss_count, 1)
+          expect(transaction).to receive(:increment)
+            .with(any_args).at_least(1) # Other calls
 
           subscriber.cache_read(event)
         end
@@ -60,8 +60,8 @@ describe Gitlab::Metrics::Subscribers::RailsCache do
           let(:event) { double(:event, duration: 15.2, payload: { hit: false, super_operation: :fetch }) }
 
           it 'does not increment cache read miss' do
-            expect(transaction).not_to receive(:increment).
-              with(:cache_read_miss_count, 1)
+            expect(transaction).not_to receive(:increment)
+              .with(:cache_read_miss_count, 1)
 
             subscriber.cache_read(event)
           end
@@ -72,8 +72,8 @@ describe Gitlab::Metrics::Subscribers::RailsCache do
 
   describe '#cache_write' do
     it 'increments the cache_write duration' do
-      expect(subscriber).to receive(:increment).
-        with(:cache_write, event.duration)
+      expect(subscriber).to receive(:increment)
+        .with(:cache_write, event.duration)
 
       subscriber.cache_write(event)
     end
@@ -81,8 +81,8 @@ describe Gitlab::Metrics::Subscribers::RailsCache do
 
   describe '#cache_delete' do
     it 'increments the cache_delete duration' do
-      expect(subscriber).to receive(:increment).
-        with(:cache_delete, event.duration)
+      expect(subscriber).to receive(:increment)
+        .with(:cache_delete, event.duration)
 
       subscriber.cache_delete(event)
     end
@@ -90,8 +90,8 @@ describe Gitlab::Metrics::Subscribers::RailsCache do
 
   describe '#cache_exist?' do
     it 'increments the cache_exists duration' do
-      expect(subscriber).to receive(:increment).
-        with(:cache_exists, event.duration)
+      expect(subscriber).to receive(:increment)
+        .with(:cache_exists, event.duration)
 
       subscriber.cache_exist?(event)
     end
@@ -108,13 +108,13 @@ describe Gitlab::Metrics::Subscribers::RailsCache do
 
     context 'with a transaction' do
       before do
-        allow(subscriber).to receive(:current_transaction).
-          and_return(transaction)
+        allow(subscriber).to receive(:current_transaction)
+          .and_return(transaction)
       end
 
       it 'increments the cache_read_hit count' do
-        expect(transaction).to receive(:increment).
-          with(:cache_read_hit_count, 1)
+        expect(transaction).to receive(:increment)
+          .with(:cache_read_hit_count, 1)
 
         subscriber.cache_fetch_hit(event)
       end
@@ -132,13 +132,13 @@ describe Gitlab::Metrics::Subscribers::RailsCache do
 
     context 'with a transaction' do
       before do
-        allow(subscriber).to receive(:current_transaction).
-          and_return(transaction)
+        allow(subscriber).to receive(:current_transaction)
+          .and_return(transaction)
       end
 
       it 'increments the cache_fetch_miss count' do
-        expect(transaction).to receive(:increment).
-          with(:cache_read_miss_count, 1)
+        expect(transaction).to receive(:increment)
+          .with(:cache_read_miss_count, 1)
 
         subscriber.cache_generate(event)
       end
@@ -156,22 +156,22 @@ describe Gitlab::Metrics::Subscribers::RailsCache do
 
     context 'with a transaction' do
       before do
-        allow(subscriber).to receive(:current_transaction).
-          and_return(transaction)
+        allow(subscriber).to receive(:current_transaction)
+          .and_return(transaction)
       end
 
       it 'increments the total and specific cache duration' do
-        expect(transaction).to receive(:increment).
-          with(:cache_duration, event.duration)
+        expect(transaction).to receive(:increment)
+          .with(:cache_duration, event.duration)
 
-        expect(transaction).to receive(:increment).
-          with(:cache_count, 1)
+        expect(transaction).to receive(:increment)
+          .with(:cache_count, 1)
 
-        expect(transaction).to receive(:increment).
-          with(:cache_delete_duration, event.duration)
+        expect(transaction).to receive(:increment)
+          .with(:cache_delete_duration, event.duration)
 
-        expect(transaction).to receive(:increment).
-          with(:cache_delete_count, 1)
+        expect(transaction).to receive(:increment)
+          .with(:cache_delete_count, 1)
 
         subscriber.increment(:cache_delete, event.duration)
       end
diff --git a/spec/lib/gitlab/metrics/transaction_spec.rb b/spec/lib/gitlab/metrics/transaction_spec.rb
index 0c5a6246d85156eed8714fc9955f5c662bdf08a7..3779af8151207b65d2e3b0d744f654b1eade0537 100644
--- a/spec/lib/gitlab/metrics/transaction_spec.rb
+++ b/spec/lib/gitlab/metrics/transaction_spec.rb
@@ -39,8 +39,8 @@ describe Gitlab::Metrics::Transaction do
 
   describe '#add_metric' do
     it 'adds a metric to the transaction' do
-      expect(Gitlab::Metrics::Metric).to receive(:new).
-        with('rails_foo', { number: 10 }, {})
+      expect(Gitlab::Metrics::Metric).to receive(:new)
+        .with('rails_foo', { number: 10 }, {})
 
       transaction.add_metric('foo', number: 10)
     end
@@ -61,8 +61,8 @@ describe Gitlab::Metrics::Transaction do
 
       values = { duration: 0.0, time: 3, allocated_memory: a_kind_of(Numeric) }
 
-      expect(transaction).to receive(:add_metric).
-        with('transactions', values, {})
+      expect(transaction).to receive(:add_metric)
+        .with('transactions', values, {})
 
       transaction.track_self
     end
@@ -78,8 +78,8 @@ describe Gitlab::Metrics::Transaction do
         allocated_memory: a_kind_of(Numeric)
       }
 
-      expect(transaction).to receive(:add_metric).
-        with('transactions', values, {})
+      expect(transaction).to receive(:add_metric)
+        .with('transactions', values, {})
 
       transaction.track_self
     end
@@ -109,8 +109,8 @@ describe Gitlab::Metrics::Transaction do
         allocated_memory: a_kind_of(Numeric)
       }
 
-      expect(transaction).to receive(:add_metric).
-        with('transactions', values, {})
+      expect(transaction).to receive(:add_metric)
+        .with('transactions', values, {})
 
       transaction.track_self
     end
@@ -120,8 +120,8 @@ describe Gitlab::Metrics::Transaction do
     it 'submits the metrics to Sidekiq' do
       transaction.track_self
 
-      expect(Gitlab::Metrics).to receive(:submit_metrics).
-        with([an_instance_of(Hash)])
+      expect(Gitlab::Metrics).to receive(:submit_metrics)
+        .with([an_instance_of(Hash)])
 
       transaction.submit
     end
@@ -137,8 +137,8 @@ describe Gitlab::Metrics::Transaction do
         timestamp: a_kind_of(Integer)
       }
 
-      expect(Gitlab::Metrics).to receive(:submit_metrics).
-        with([hash])
+      expect(Gitlab::Metrics).to receive(:submit_metrics)
+        .with([hash])
 
       transaction.submit
     end
@@ -154,8 +154,8 @@ describe Gitlab::Metrics::Transaction do
         timestamp: a_kind_of(Integer)
       }
 
-      expect(Gitlab::Metrics).to receive(:submit_metrics).
-        with([hash])
+      expect(Gitlab::Metrics).to receive(:submit_metrics)
+        .with([hash])
 
       transaction.submit
     end
diff --git a/spec/lib/gitlab/metrics/unicorn_sampler_spec.rb b/spec/lib/gitlab/metrics/unicorn_sampler_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..dc0d1f2e940ad8c1cc49506b6fb3f5ee3a84d356
--- /dev/null
+++ b/spec/lib/gitlab/metrics/unicorn_sampler_spec.rb
@@ -0,0 +1,108 @@
+require 'spec_helper'
+
+describe Gitlab::Metrics::UnicornSampler do
+  subject { described_class.new(1.second) }
+
+  describe '#sample' do
+    let(:unicorn) { double('unicorn') }
+    let(:raindrops) { double('raindrops') }
+    let(:stats) { double('stats') }
+
+    before do
+      stub_const('Unicorn', unicorn)
+      stub_const('Raindrops::Linux', raindrops)
+      allow(raindrops).to receive(:unix_listener_stats).and_return({})
+      allow(raindrops).to receive(:tcp_listener_stats).and_return({})
+    end
+
+    context 'unicorn listens on unix sockets' do
+      let(:socket_address) { '/some/sock' }
+      let(:sockets) { [socket_address] }
+
+      before do
+        allow(unicorn).to receive(:listener_names).and_return(sockets)
+      end
+
+      it 'samples socket data' do
+        expect(raindrops).to receive(:unix_listener_stats).with(sockets)
+
+        subject.sample
+      end
+
+      context 'stats collected' do
+        before do
+          allow(stats).to receive(:active).and_return('active')
+          allow(stats).to receive(:queued).and_return('queued')
+          allow(raindrops).to receive(:unix_listener_stats).and_return({ socket_address => stats })
+        end
+
+        it 'updates metrics type unix and with addr' do
+          labels = { type: 'unix', address: socket_address }
+
+          expect(subject).to receive_message_chain(:unicorn_active_connections, :set).with(labels, 'active')
+          expect(subject).to receive_message_chain(:unicorn_queued_connections, :set).with(labels, 'queued')
+
+          subject.sample
+        end
+      end
+    end
+
+    context 'unicorn listens on tcp sockets' do
+      let(:tcp_socket_address) { '0.0.0.0:8080' }
+      let(:tcp_sockets) { [tcp_socket_address] }
+
+      before do
+        allow(unicorn).to receive(:listener_names).and_return(tcp_sockets)
+      end
+
+      it 'samples socket data' do
+        expect(raindrops).to receive(:tcp_listener_stats).with(tcp_sockets)
+
+        subject.sample
+      end
+
+      context 'stats collected' do
+        before do
+          allow(stats).to receive(:active).and_return('active')
+          allow(stats).to receive(:queued).and_return('queued')
+          allow(raindrops).to receive(:tcp_listener_stats).and_return({ tcp_socket_address => stats })
+        end
+
+        it 'updates metrics type unix and with addr' do
+          labels = { type: 'tcp', address: tcp_socket_address }
+
+          expect(subject).to receive_message_chain(:unicorn_active_connections, :set).with(labels, 'active')
+          expect(subject).to receive_message_chain(:unicorn_queued_connections, :set).with(labels, 'queued')
+
+          subject.sample
+        end
+      end
+    end
+  end
+
+  describe '#start' do
+    context 'when enabled' do
+      before do
+        allow(subject).to receive(:enabled?).and_return(true)
+      end
+
+      it 'creates new thread' do
+        expect(Thread).to receive(:new)
+
+        subject.start
+      end
+    end
+
+    context 'when disabled' do
+      before do
+        allow(subject).to receive(:enabled?).and_return(false)
+      end
+
+      it "doesn't create new thread" do
+        expect(Thread).not_to receive(:new)
+
+        subject.start
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/metrics_spec.rb b/spec/lib/gitlab/metrics_spec.rb
index 5a87b906609b2e8d799c89315cfec6d430b5b352..599b8807d8dbf281efd01f5870deb7fdc89ce1f3 100644
--- a/spec/lib/gitlab/metrics_spec.rb
+++ b/spec/lib/gitlab/metrics_spec.rb
@@ -15,6 +15,36 @@ describe Gitlab::Metrics do
     end
   end
 
+  describe '.prometheus_metrics_enabled_unmemoized' do
+    subject { described_class.send(:prometheus_metrics_enabled_unmemoized) }
+
+    context 'prometheus metrics enabled in config' do
+      before do
+        allow(described_class).to receive(:current_application_settings).and_return(prometheus_metrics_enabled: true)
+      end
+
+      context 'when metrics folder is present' do
+        before do
+          allow(described_class).to receive(:metrics_folder_present?).and_return(true)
+        end
+
+        it 'metrics are enabled' do
+          expect(subject).to eq(true)
+        end
+      end
+
+      context 'when metrics folder is missing' do
+        before do
+          allow(described_class).to receive(:metrics_folder_present?).and_return(false)
+        end
+
+        it 'metrics are disabled' do
+          expect(subject).to eq(false)
+        end
+      end
+    end
+  end
+
   describe '.prometheus_metrics_enabled?' do
     it 'returns a boolean' do
       expect(described_class.prometheus_metrics_enabled?).to be_in([true, false])
@@ -42,8 +72,8 @@ describe Gitlab::Metrics do
 
   describe '.prepare_metrics' do
     it 'returns a Hash with the keys as Symbols' do
-      metrics = described_class.
-        prepare_metrics([{ 'values' => {}, 'tags' => {} }])
+      metrics = described_class
+        .prepare_metrics([{ 'values' => {}, 'tags' => {} }])
 
       expect(metrics).to eq([{ values: {}, tags: {} }])
     end
@@ -88,19 +118,19 @@ describe Gitlab::Metrics do
       let(:transaction) { Gitlab::Metrics::Transaction.new }
 
       before do
-        allow(described_class).to receive(:current_transaction).
-          and_return(transaction)
+        allow(described_class).to receive(:current_transaction)
+          .and_return(transaction)
       end
 
       it 'adds a metric to the current transaction' do
-        expect(transaction).to receive(:increment).
-          with('foo_real_time', a_kind_of(Numeric))
+        expect(transaction).to receive(:increment)
+          .with('foo_real_time', a_kind_of(Numeric))
 
-        expect(transaction).to receive(:increment).
-          with('foo_cpu_time', a_kind_of(Numeric))
+        expect(transaction).to receive(:increment)
+          .with('foo_cpu_time', a_kind_of(Numeric))
 
-        expect(transaction).to receive(:increment).
-          with('foo_call_count', 1)
+        expect(transaction).to receive(:increment)
+          .with('foo_call_count', 1)
 
         described_class.measure(:foo) { 10 }
       end
@@ -116,8 +146,8 @@ describe Gitlab::Metrics do
   describe '.tag_transaction' do
     context 'without a transaction' do
       it 'does nothing' do
-        expect_any_instance_of(Gitlab::Metrics::Transaction).
-          not_to receive(:add_tag)
+        expect_any_instance_of(Gitlab::Metrics::Transaction)
+          .not_to receive(:add_tag)
 
         described_class.tag_transaction(:foo, 'bar')
       end
@@ -127,11 +157,11 @@ describe Gitlab::Metrics do
       let(:transaction) { Gitlab::Metrics::Transaction.new }
 
       it 'adds the tag to the transaction' do
-        expect(described_class).to receive(:current_transaction).
-          and_return(transaction)
+        expect(described_class).to receive(:current_transaction)
+          .and_return(transaction)
 
-        expect(transaction).to receive(:add_tag).
-          with(:foo, 'bar')
+        expect(transaction).to receive(:add_tag)
+          .with(:foo, 'bar')
 
         described_class.tag_transaction(:foo, 'bar')
       end
@@ -141,8 +171,8 @@ describe Gitlab::Metrics do
   describe '.action=' do
     context 'without a transaction' do
       it 'does nothing' do
-        expect_any_instance_of(Gitlab::Metrics::Transaction).
-          not_to receive(:action=)
+        expect_any_instance_of(Gitlab::Metrics::Transaction)
+          .not_to receive(:action=)
 
         described_class.action = 'foo'
       end
@@ -152,8 +182,8 @@ describe Gitlab::Metrics do
       it 'sets the action of a transaction' do
         trans = Gitlab::Metrics::Transaction.new
 
-        expect(described_class).to receive(:current_transaction).
-          and_return(trans)
+        expect(described_class).to receive(:current_transaction)
+          .and_return(trans)
 
         expect(trans).to receive(:action=).with('foo')
 
@@ -171,8 +201,8 @@ describe Gitlab::Metrics do
   describe '.add_event' do
     context 'without a transaction' do
       it 'does nothing' do
-        expect_any_instance_of(Gitlab::Metrics::Transaction).
-          not_to receive(:add_event)
+        expect_any_instance_of(Gitlab::Metrics::Transaction)
+          .not_to receive(:add_event)
 
         described_class.add_event(:meow)
       end
@@ -184,8 +214,8 @@ describe Gitlab::Metrics do
 
         expect(transaction).to receive(:add_event).with(:meow)
 
-        expect(described_class).to receive(:current_transaction).
-          and_return(transaction)
+        expect(described_class).to receive(:current_transaction)
+          .and_return(transaction)
 
         described_class.add_event(:meow)
       end
diff --git a/spec/lib/gitlab/popen_spec.rb b/spec/lib/gitlab/popen_spec.rb
index 4ae216d55b0b3bcfc7232b11f348d3628c99580f..af50ecdb2ab16a09679506f55ba9401108bb0533 100644
--- a/spec/lib/gitlab/popen_spec.rb
+++ b/spec/lib/gitlab/popen_spec.rb
@@ -32,6 +32,17 @@ describe 'Gitlab::Popen', lib: true, no_db: true do
     end
   end
 
+  context 'with custom options' do
+    let(:vars) { { 'foobar' => 123, 'PWD' => path } }
+    let(:options) { { chdir: path } }
+
+    it 'calls popen3 with the provided environment variables' do
+      expect(Open3).to receive(:popen3).with(vars, 'ls', options)
+
+      @output, @status = @klass.new.popen(%w(ls), path, { 'foobar' => 123 })
+    end
+  end
+
   context 'without a directory argument' do
     before do
       @output, @status = @klass.new.popen(%w(ls))
@@ -45,7 +56,7 @@ describe 'Gitlab::Popen', lib: true, no_db: true do
     before do
       @output, @status = @klass.new.popen(%w[cat]) { |stdin| stdin.write 'hello' }
     end
-  
+
     it { expect(@status).to be_zero }
     it { expect(@output).to eq('hello') }
   end
diff --git a/spec/lib/gitlab/project_authorizations_spec.rb b/spec/lib/gitlab/project_authorizations_spec.rb
index 67321f43710bef3b1a094a5f61c4ccabca516cfc..9ce33685697f9576f4b6a3ed4b912cf67b51262d 100644
--- a/spec/lib/gitlab/project_authorizations_spec.rb
+++ b/spec/lib/gitlab/project_authorizations_spec.rb
@@ -34,8 +34,8 @@ describe Gitlab::ProjectAuthorizations do
   end
 
   it 'includes the correct projects' do
-    expect(authorizations.pluck(:project_id)).
-      to include(owned_project.id, other_project.id, group_project.id)
+    expect(authorizations.pluck(:project_id))
+      .to include(owned_project.id, other_project.id, group_project.id)
   end
 
   it 'includes the correct access levels' do
diff --git a/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb b/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..61d48b05454c75ee4a2a3265a64e28f2c6322cd5
--- /dev/null
+++ b/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb
@@ -0,0 +1,246 @@
+require 'spec_helper'
+
+describe Gitlab::Prometheus::AdditionalMetricsParser, lib: true do
+  include Prometheus::MetricBuilders
+
+  let(:parser_error_class) { Gitlab::Prometheus::ParsingError }
+
+  describe '#load_groups_from_yaml' do
+    subject { described_class.load_groups_from_yaml }
+
+    describe 'parsing sample yaml' do
+      let(:sample_yaml) do
+        <<-EOF.strip_heredoc
+          - group: group_a
+            priority: 1
+            metrics:
+              - title: "title"
+                required_metrics: [ metric_a, metric_b ]
+                weight: 1
+                queries: [{ query_range: 'query_range_a', label: label, unit: unit }]
+              - title: "title"
+                required_metrics: [metric_a]
+                weight: 1
+                queries: [{ query_range: 'query_range_empty' }]
+          - group: group_b
+            priority: 1
+            metrics: 
+              - title: title
+                required_metrics: ['metric_a']
+                weight: 1
+                queries: [{query_range: query_range_a}]
+        EOF
+      end
+
+      before do
+        allow(described_class).to receive(:load_yaml_file) { YAML.load(sample_yaml) }
+      end
+
+      it 'parses to two metric groups with 2 and 1 metric respectively' do
+        expect(subject.count).to eq(2)
+        expect(subject[0].metrics.count).to eq(2)
+        expect(subject[1].metrics.count).to eq(1)
+      end
+
+      it 'provide group data' do
+        expect(subject[0]).to have_attributes(name: 'group_a', priority: 1)
+        expect(subject[1]).to have_attributes(name: 'group_b', priority: 1)
+      end
+
+      it 'provides metrics data' do
+        metrics = subject.flat_map(&:metrics)
+
+        expect(metrics.count).to eq(3)
+        expect(metrics[0]).to have_attributes(title: 'title', required_metrics: %w(metric_a metric_b), weight: 1)
+        expect(metrics[1]).to have_attributes(title: 'title', required_metrics: %w(metric_a), weight: 1)
+        expect(metrics[2]).to have_attributes(title: 'title', required_metrics: %w{metric_a}, weight: 1)
+      end
+
+      it 'provides query data' do
+        queries = subject.flat_map(&:metrics).flat_map(&:queries)
+
+        expect(queries.count).to eq(3)
+        expect(queries[0]).to eq(query_range: 'query_range_a', label: 'label', unit: 'unit')
+        expect(queries[1]).to eq(query_range: 'query_range_empty')
+        expect(queries[2]).to eq(query_range: 'query_range_a')
+      end
+    end
+
+    shared_examples 'required field' do |field_name|
+      context "when #{field_name} is nil" do
+        before do
+          allow(described_class).to receive(:load_yaml_file) { YAML.load(field_missing) }
+        end
+
+        it 'throws parsing error' do
+          expect { subject }.to raise_error(parser_error_class, /#{field_name} can't be blank/i)
+        end
+      end
+
+      context "when #{field_name} are not specified" do
+        before do
+          allow(described_class).to receive(:load_yaml_file) { YAML.load(field_nil) }
+        end
+
+        it 'throws parsing error' do
+          expect { subject }.to raise_error(parser_error_class, /#{field_name} can't be blank/i)
+        end
+      end
+    end
+
+    describe 'group required fields' do
+      it_behaves_like 'required field', 'metrics' do
+        let(:field_nil) do
+          <<-EOF.strip_heredoc
+            - group: group_a
+              priority: 1
+              metrics:
+          EOF
+        end
+
+        let(:field_missing) do
+          <<-EOF.strip_heredoc
+            - group: group_a
+              priority: 1
+          EOF
+        end
+      end
+
+      it_behaves_like 'required field', 'name' do
+        let(:field_nil) do
+          <<-EOF.strip_heredoc
+            - group:
+              priority: 1
+              metrics: []
+          EOF
+        end
+
+        let(:field_missing) do
+          <<-EOF.strip_heredoc
+            - priority: 1
+              metrics: []
+          EOF
+        end
+      end
+
+      it_behaves_like 'required field', 'priority' do
+        let(:field_nil) do
+          <<-EOF.strip_heredoc
+            - group: group_a
+              priority:
+              metrics: []
+          EOF
+        end
+
+        let(:field_missing) do
+          <<-EOF.strip_heredoc
+            - group: group_a
+              metrics: []
+          EOF
+        end
+      end
+    end
+
+    describe 'metrics fields parsing' do
+      it_behaves_like 'required field', 'title' do
+        let(:field_nil) do
+          <<-EOF.strip_heredoc
+            - group: group_a
+              priority: 1
+              metrics:
+              - title: 
+                required_metrics: []
+                weight: 1
+                queries: []
+          EOF
+        end
+
+        let(:field_missing) do
+          <<-EOF.strip_heredoc
+            - group: group_a
+              priority: 1
+              metrics:
+              - required_metrics: []
+                weight: 1
+                queries: []
+          EOF
+        end
+      end
+
+      it_behaves_like 'required field', 'required metrics' do
+        let(:field_nil) do
+          <<-EOF.strip_heredoc
+            - group: group_a
+              priority: 1
+              metrics:
+              - title: title
+                required_metrics:
+                weight: 1
+                queries: []
+          EOF
+        end
+
+        let(:field_missing) do
+          <<-EOF.strip_heredoc
+            - group: group_a
+              priority: 1
+              metrics:
+              - title: title
+                weight: 1
+                queries: []
+          EOF
+        end
+      end
+
+      it_behaves_like 'required field', 'weight' do
+        let(:field_nil) do
+          <<-EOF.strip_heredoc
+            - group: group_a
+              priority: 1
+              metrics:
+              - title: title
+                required_metrics: []
+                weight:
+                queries: []
+          EOF
+        end
+
+        let(:field_missing) do
+          <<-EOF.strip_heredoc
+            - group: group_a
+              priority: 1
+              metrics:
+              - title: title
+                required_metrics: []
+                queries: []
+          EOF
+        end
+      end
+
+      it_behaves_like 'required field', :queries do
+        let(:field_nil) do
+          <<-EOF.strip_heredoc
+            - group: group_a
+              priority: 1
+              metrics:
+              - title: title
+                required_metrics: []
+                weight: 1
+                queries:
+          EOF
+        end
+
+        let(:field_missing) do
+          <<-EOF.strip_heredoc
+            - group: group_a
+              priority: 1
+              metrics:
+              - title: title
+                required_metrics: []
+                weight: 1
+          EOF
+        end
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb b/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4909aec5a4d5da3526439d9da3075f9ca99ec359
--- /dev/null
+++ b/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe Gitlab::Prometheus::Queries::AdditionalMetricsDeploymentQuery, lib: true do
+  include Prometheus::MetricBuilders
+
+  let(:client) { double('prometheus_client') }
+  let(:environment) { create(:environment, slug: 'environment-slug') }
+  let(:deployment) { create(:deployment, environment: environment) }
+
+  subject(:query_result) { described_class.new(client).query(deployment.id) }
+
+  around do |example|
+    Timecop.freeze(Time.local(2008, 9, 1, 12, 0, 0)) { example.run }
+  end
+
+  include_examples 'additional metrics query' do
+    it 'queries using specific time' do
+      expect(client).to receive(:query_range).with(anything,
+                                                   start: (deployment.created_at - 30.minutes).to_f,
+                                                   stop: (deployment.created_at + 30.minutes).to_f)
+
+      expect(query_result).not_to be_nil
+    end
+  end
+end
diff --git a/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb b/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8e6e3bb594678c2df45127747afe5ae6228de4a1
--- /dev/null
+++ b/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe Gitlab::Prometheus::Queries::AdditionalMetricsEnvironmentQuery, lib: true do
+  include Prometheus::MetricBuilders
+
+  let(:client) { double('prometheus_client') }
+  let(:environment) { create(:environment, slug: 'environment-slug') }
+
+  subject(:query_result) { described_class.new(client).query(environment.id) }
+
+  around do |example|
+    Timecop.freeze { example.run }
+  end
+
+  include_examples 'additional metrics query' do
+    it 'queries using specific time' do
+      expect(client).to receive(:query_range).with(anything, start: 8.hours.ago.to_f, stop: Time.now.to_f)
+      expect(query_result).not_to be_nil
+    end
+  end
+end
diff --git a/spec/lib/gitlab/prometheus/queries/matched_metrics_query_spec.rb b/spec/lib/gitlab/prometheus/queries/matched_metrics_query_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d2796ab72da26811d59ad34908227ae33fd3a434
--- /dev/null
+++ b/spec/lib/gitlab/prometheus/queries/matched_metrics_query_spec.rb
@@ -0,0 +1,134 @@
+require 'spec_helper'
+
+describe Gitlab::Prometheus::Queries::MatchedMetricsQuery, lib: true do
+  include Prometheus::MetricBuilders
+
+  let(:metric_group_class) { Gitlab::Prometheus::MetricGroup }
+  let(:metric_class) { Gitlab::Prometheus::Metric }
+
+  def series_info_with_environment(*more_metrics)
+    %w{metric_a metric_b}.concat(more_metrics).map { |metric_name| { '__name__' => metric_name, 'environment' => '' } }
+  end
+
+  let(:metric_names) { %w{metric_a metric_b} }
+  let(:series_info_without_environment) do
+    [{ '__name__' => 'metric_a' },
+     { '__name__' => 'metric_b' }]
+  end
+  let(:partialy_empty_series_info) { [{ '__name__' => 'metric_a', 'environment' => '' }] }
+  let(:empty_series_info) { [] }
+
+  let(:client) { double('prometheus_client') }
+
+  subject { described_class.new(client) }
+
+  context 'with one group where two metrics is found' do
+    before do
+      allow(metric_group_class).to receive(:all).and_return([simple_metric_group])
+      allow(client).to receive(:label_values).and_return(metric_names)
+    end
+
+    context 'both metrics in the group pass requirements' do
+      before do
+        allow(client).to receive(:series).and_return(series_info_with_environment)
+      end
+
+      it 'responds with both metrics as actve' do
+        expect(subject.query).to eq([{ group: 'name', priority: 1, active_metrics: 2, metrics_missing_requirements: 0 }])
+      end
+    end
+
+    context 'none of the metrics pass requirements' do
+      before do
+        allow(client).to receive(:series).and_return(series_info_without_environment)
+      end
+
+      it 'responds with both metrics missing requirements' do
+        expect(subject.query).to eq([{ group: 'name', priority: 1, active_metrics: 0, metrics_missing_requirements: 2 }])
+      end
+    end
+
+    context 'no series information found about the metrics' do
+      before do
+        allow(client).to receive(:series).and_return(empty_series_info)
+      end
+
+      it 'responds with both metrics missing requirements' do
+        expect(subject.query).to eq([{ group: 'name', priority: 1, active_metrics: 0, metrics_missing_requirements: 2 }])
+      end
+    end
+
+    context 'one of the series info was not found' do
+      before do
+        allow(client).to receive(:series).and_return(partialy_empty_series_info)
+      end
+      it 'responds with one active and one missing metric' do
+        expect(subject.query).to eq([{ group: 'name', priority: 1, active_metrics: 1, metrics_missing_requirements: 1 }])
+      end
+    end
+  end
+
+  context 'with one group where only one metric is found' do
+    before do
+      allow(metric_group_class).to receive(:all).and_return([simple_metric_group])
+      allow(client).to receive(:label_values).and_return('metric_a')
+    end
+
+    context 'both metrics in the group pass requirements' do
+      before do
+        allow(client).to receive(:series).and_return(series_info_with_environment)
+      end
+
+      it 'responds with one metrics as active and no missing requiremens' do
+        expect(subject.query).to eq([{ group: 'name', priority: 1, active_metrics: 1, metrics_missing_requirements: 0 }])
+      end
+    end
+
+    context 'no metrics in group pass requirements' do
+      before do
+        allow(client).to receive(:series).and_return(series_info_without_environment)
+      end
+
+      it 'responds with one metrics as active and no missing requiremens' do
+        expect(subject.query).to eq([{ group: 'name', priority: 1, active_metrics: 0, metrics_missing_requirements: 1 }])
+      end
+    end
+  end
+
+  context 'with two groups where metrics are found in each group' do
+    let(:second_metric_group) { simple_metric_group(name: 'nameb', metrics: simple_metrics(added_metric_name: 'metric_c')) }
+
+    before do
+      allow(metric_group_class).to receive(:all).and_return([simple_metric_group, second_metric_group])
+      allow(client).to receive(:label_values).and_return('metric_c')
+    end
+
+    context 'all metrics in both groups pass requirements' do
+      before do
+        allow(client).to receive(:series).and_return(series_info_with_environment('metric_c'))
+      end
+
+      it 'responds with one metrics as active and no missing requiremens' do
+        expect(subject.query).to eq([
+                                      { group: 'name', priority: 1, active_metrics: 1, metrics_missing_requirements: 0 },
+                                      { group: 'nameb', priority: 1, active_metrics: 2, metrics_missing_requirements: 0 }
+                                    ]
+                                   )
+      end
+    end
+
+    context 'no metrics in groups pass requirements' do
+      before do
+        allow(client).to receive(:series).and_return(series_info_without_environment)
+      end
+
+      it 'responds with one metrics as active and no missing requiremens' do
+        expect(subject.query).to eq([
+                                      { group: 'name', priority: 1, active_metrics: 0, metrics_missing_requirements: 1 },
+                                      { group: 'nameb', priority: 1, active_metrics: 0, metrics_missing_requirements: 2 }
+                                    ]
+                                   )
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/prometheus_client_spec.rb b/spec/lib/gitlab/prometheus_client_spec.rb
index 2d8bd2f6b978e791b66da3cadefb0121953ad99c..46eaadae2065a64290f9fe2acbea1396f4e8f00d 100644
--- a/spec/lib/gitlab/prometheus_client_spec.rb
+++ b/spec/lib/gitlab/prometheus_client_spec.rb
@@ -119,6 +119,36 @@ describe Gitlab::PrometheusClient, lib: true do
     end
   end
 
+  describe '#series' do
+    let(:query_url) { prometheus_series_url('series_name', 'other_service') }
+
+    around do |example|
+      Timecop.freeze { example.run }
+    end
+
+    it 'calls endpoint and returns list of series' do
+      req_stub = stub_prometheus_request(query_url, body: prometheus_series('series_name'))
+      expected = prometheus_series('series_name').deep_stringify_keys['data']
+
+      expect(subject.series('series_name', 'other_service')).to eq(expected)
+
+      expect(req_stub).to have_been_requested
+    end
+  end
+
+  describe '#label_values' do
+    let(:query_url) { prometheus_label_values_url('__name__') }
+
+    it 'calls endpoint and returns label values' do
+      req_stub = stub_prometheus_request(query_url, body: prometheus_label_values)
+      expected = prometheus_label_values.deep_stringify_keys['data']
+
+      expect(subject.label_values('__name__')).to eq(expected)
+
+      expect(req_stub).to have_been_requested
+    end
+  end
+
   describe '#query_range' do
     let(:prometheus_query) { prometheus_memory_query('env-slug') }
     let(:query_url) { prometheus_query_range_url(prometheus_query) }
diff --git a/spec/lib/gitlab/slash_commands/command_definition_spec.rb b/spec/lib/gitlab/quick_actions/command_definition_spec.rb
similarity index 99%
rename from spec/lib/gitlab/slash_commands/command_definition_spec.rb
rename to spec/lib/gitlab/quick_actions/command_definition_spec.rb
index 5b9173d3d3f515710be8cb64bec28e5859a90025..f44a562dc633b564573a3a0cea79619b5c567475 100644
--- a/spec/lib/gitlab/slash_commands/command_definition_spec.rb
+++ b/spec/lib/gitlab/quick_actions/command_definition_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::SlashCommands::CommandDefinition do
+describe Gitlab::QuickActions::CommandDefinition do
   subject { described_class.new(:command) }
 
   describe "#all_names" do
diff --git a/spec/lib/gitlab/slash_commands/dsl_spec.rb b/spec/lib/gitlab/quick_actions/dsl_spec.rb
similarity index 97%
rename from spec/lib/gitlab/slash_commands/dsl_spec.rb
rename to spec/lib/gitlab/quick_actions/dsl_spec.rb
index 33b49a5ddf98bd475dcfe3b81a4d2653243d069c..a4bb3f911d7e9953943ad7d0cd3b4b5e57f2b40e 100644
--- a/spec/lib/gitlab/slash_commands/dsl_spec.rb
+++ b/spec/lib/gitlab/quick_actions/dsl_spec.rb
@@ -1,9 +1,9 @@
 require 'spec_helper'
 
-describe Gitlab::SlashCommands::Dsl do
+describe Gitlab::QuickActions::Dsl do
   before :all do
     DummyClass = Struct.new(:project) do
-      include Gitlab::SlashCommands::Dsl # rubocop:disable RSpec/DescribedClass
+      include Gitlab::QuickActions::Dsl # rubocop:disable RSpec/DescribedClass
 
       desc 'A command with no args'
       command :no_args, :none do
diff --git a/spec/lib/gitlab/slash_commands/extractor_spec.rb b/spec/lib/gitlab/quick_actions/extractor_spec.rb
similarity index 98%
rename from spec/lib/gitlab/slash_commands/extractor_spec.rb
rename to spec/lib/gitlab/quick_actions/extractor_spec.rb
index d7f77486b3e6ebab612aed81d4b861d3e297fd45..9d32938e155a5f440266ae290f3af2dc60451555 100644
--- a/spec/lib/gitlab/slash_commands/extractor_spec.rb
+++ b/spec/lib/gitlab/quick_actions/extractor_spec.rb
@@ -1,9 +1,9 @@
 require 'spec_helper'
 
-describe Gitlab::SlashCommands::Extractor do
+describe Gitlab::QuickActions::Extractor do
   let(:definitions) do
     Class.new do
-      include Gitlab::SlashCommands::Dsl
+      include Gitlab::QuickActions::Dsl
 
       command(:reopen, :open) { }
       command(:assign) { }
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index 0bee892fe0c3afc5fb5e718d506bc8108294566c..51e2c3c38c62b612ce87a1f127ab3d3313bd3c98 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -14,10 +14,16 @@ describe Gitlab::Regex, lib: true do
     it { is_expected.not_to match('?gitlab') }
   end
 
-  describe '.file_name_regex' do
-    subject { described_class.file_name_regex }
+  describe '.environment_slug_regex' do
+    subject { described_class.environment_name_regex }
 
-    it { is_expected.to match('foo@bar') }
+    it { is_expected.to match('foo') }
+    it { is_expected.to match('foo-1') }
+    it { is_expected.to match('FOO') }
+    it { is_expected.to match('foo/1') }
+    it { is_expected.to match('foo.1') }
+    it { is_expected.not_to match('9&foo') }
+    it { is_expected.not_to match('foo-^') }
   end
 
   describe '.environment_slug_regex' do
diff --git a/spec/lib/gitlab/repo_path_spec.rb b/spec/lib/gitlab/repo_path_spec.rb
index f90253971075d031ece9d211ab73e122eb6fa69b..efea4f429bfa358924d11430d5e4dda186b8cf24 100644
--- a/spec/lib/gitlab/repo_path_spec.rb
+++ b/spec/lib/gitlab/repo_path_spec.rb
@@ -4,24 +4,44 @@ describe ::Gitlab::RepoPath do
   describe '.parse' do
     set(:project) { create(:project) }
 
-    it 'parses a full repository path' do
-      expect(described_class.parse(project.repository.path)).to eq([project, false])
-    end
+    context 'a repository storage path' do
+      it 'parses a full repository path' do
+        expect(described_class.parse(project.repository.path)).to eq([project, false, nil])
+      end
 
-    it 'parses a full wiki path' do
-      expect(described_class.parse(project.wiki.repository.path)).to eq([project, true])
+      it 'parses a full wiki path' do
+        expect(described_class.parse(project.wiki.repository.path)).to eq([project, true, nil])
+      end
     end
 
-    it 'parses a relative repository path' do
-      expect(described_class.parse(project.full_path + '.git')).to eq([project, false])
-    end
+    context 'a relative path' do
+      it 'parses a relative repository path' do
+        expect(described_class.parse(project.full_path + '.git')).to eq([project, false, nil])
+      end
 
-    it 'parses a relative wiki path' do
-      expect(described_class.parse(project.full_path + '.wiki.git')).to eq([project, true])
-    end
+      it 'parses a relative wiki path' do
+        expect(described_class.parse(project.full_path + '.wiki.git')).to eq([project, true, nil])
+      end
+
+      it 'parses a relative path starting with /' do
+        expect(described_class.parse('/' + project.full_path + '.git')).to eq([project, false, nil])
+      end
+
+      context 'of a redirected project' do
+        let(:redirect) { project.route.create_redirect('foo/bar') }
+
+        it 'parses a relative repository path' do
+          expect(described_class.parse(redirect.path + '.git')).to eq([project, false, 'foo/bar'])
+        end
+
+        it 'parses a relative wiki path' do
+          expect(described_class.parse(redirect.path + '.wiki.git')).to eq([project, true, 'foo/bar.wiki'])
+        end
 
-    it 'parses a relative path starting with /' do
-      expect(described_class.parse('/' + project.full_path + '.git')).to eq([project, false])
+        it 'parses a relative path starting with /' do
+          expect(described_class.parse('/' + redirect.path + '.git')).to eq([project, false, 'foo/bar'])
+        end
+      end
     end
   end
 
@@ -43,4 +63,33 @@ describe ::Gitlab::RepoPath do
       )
     end
   end
+
+  describe '.find_project' do
+    let(:project) { create(:empty_project) }
+    let(:redirect) { project.route.create_redirect('foo/bar/baz') }
+
+    context 'when finding a project by its canonical path' do
+      context 'when the cases match' do
+        it 'returns the project and false' do
+          expect(described_class.find_project(project.full_path)).to eq([project, false])
+        end
+      end
+
+      context 'when the cases do not match' do
+        # This is slightly different than web behavior because on the web it is
+        # easy and safe to redirect someone to the correctly-cased URL. For git
+        # requests, we should accept wrongly-cased URLs because it is a pain to
+        # block people's git operations and force them to update remote URLs.
+        it 'returns the project and false' do
+          expect(described_class.find_project(project.full_path.upcase)).to eq([project, false])
+        end
+      end
+    end
+
+    context 'when finding a project via a redirect' do
+      it 'returns the project and true' do
+        expect(described_class.find_project(redirect.path)).to eq([project, true])
+      end
+    end
+  end
 end
diff --git a/spec/lib/gitlab/route_map_spec.rb b/spec/lib/gitlab/route_map_spec.rb
index 2370f56a6131a994a7c27e0c7e74456a519b2029..21c00c6e5b80a3c511bb74e5364c54cbc4499ff2 100644
--- a/spec/lib/gitlab/route_map_spec.rb
+++ b/spec/lib/gitlab/route_map_spec.rb
@@ -4,43 +4,43 @@ describe Gitlab::RouteMap, lib: true do
   describe '#initialize' do
     context 'when the data is not YAML' do
       it 'raises an error' do
-        expect { described_class.new('"') }.
-          to raise_error(Gitlab::RouteMap::FormatError, /valid YAML/)
+        expect { described_class.new('"') }
+          .to raise_error(Gitlab::RouteMap::FormatError, /valid YAML/)
       end
     end
 
     context 'when the data is not a YAML array' do
       it 'raises an error' do
-        expect { described_class.new(YAML.dump('foo')) }.
-          to raise_error(Gitlab::RouteMap::FormatError, /an array/)
+        expect { described_class.new(YAML.dump('foo')) }
+          .to raise_error(Gitlab::RouteMap::FormatError, /an array/)
       end
     end
 
     context 'when an entry is not a hash' do
       it 'raises an error' do
-        expect { described_class.new(YAML.dump(['foo'])) }.
-          to raise_error(Gitlab::RouteMap::FormatError, /a hash/)
+        expect { described_class.new(YAML.dump(['foo'])) }
+          .to raise_error(Gitlab::RouteMap::FormatError, /a hash/)
       end
     end
 
     context 'when an entry does not have a source key' do
       it 'raises an error' do
-        expect { described_class.new(YAML.dump([{ 'public' => 'index.html' }])) }.
-          to raise_error(Gitlab::RouteMap::FormatError, /source key/)
+        expect { described_class.new(YAML.dump([{ 'public' => 'index.html' }])) }
+          .to raise_error(Gitlab::RouteMap::FormatError, /source key/)
       end
     end
 
     context 'when an entry does not have a public key' do
       it 'raises an error' do
-        expect { described_class.new(YAML.dump([{ 'source' => '/index\.html/' }])) }.
-          to raise_error(Gitlab::RouteMap::FormatError, /public key/)
+        expect { described_class.new(YAML.dump([{ 'source' => '/index\.html/' }])) }
+          .to raise_error(Gitlab::RouteMap::FormatError, /public key/)
       end
     end
 
     context 'when an entry source is not a valid regex' do
       it 'raises an error' do
-        expect { described_class.new(YAML.dump([{ 'source' => '/[/', 'public' => 'index.html' }])) }.
-          to raise_error(Gitlab::RouteMap::FormatError, /regular expression/)
+        expect { described_class.new(YAML.dump([{ 'source' => '/[/', 'public' => 'index.html' }])) }
+          .to raise_error(Gitlab::RouteMap::FormatError, /regular expression/)
       end
     end
 
diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
index a97a0f8452bd74c2a9a993da664c42373a1bd1af..5b1b8f9516a245c5886b9affe2b246f34e8154a8 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -4,6 +4,7 @@ require 'stringio'
 describe Gitlab::Shell, lib: true do
   let(:project) { double('Project', id: 7, path: 'diaspora') }
   let(:gitlab_shell) { Gitlab::Shell.new }
+  let(:popen_vars) { { 'GIT_TERMINAL_PROMPT' => ENV['GIT_TERMINAL_PROMPT'] } }
 
   before do
     allow(Project).to receive(:find).and_return(project)
@@ -50,7 +51,7 @@ describe Gitlab::Shell, lib: true do
   describe '#add_key' do
     it 'removes trailing garbage' do
       allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
-      expect(Gitlab::Utils).to receive(:system_silent).with(
+      expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
         [:gitlab_shell_keys_path, 'add-key', 'key-123', 'ssh-rsa foobar']
       )
 
@@ -100,17 +101,91 @@ describe Gitlab::Shell, lib: true do
       allow(Gitlab.config.gitlab_shell).to receive(:git_timeout).and_return(800)
     end
 
+    describe '#add_repository' do
+      it 'returns true when the command succeeds' do
+        expect(Gitlab::Popen).to receive(:popen)
+          .with([projects_path, 'add-project', 'current/storage', 'project/path.git'],
+                nil, popen_vars).and_return([nil, 0])
+
+        expect(gitlab_shell.add_repository('current/storage', 'project/path')).to be true
+      end
+
+      it 'returns false when the command fails' do
+        expect(Gitlab::Popen).to receive(:popen)
+          .with([projects_path, 'add-project', 'current/storage', 'project/path.git'],
+                nil, popen_vars).and_return(["error", 1])
+
+        expect(gitlab_shell.add_repository('current/storage', 'project/path')).to be false
+      end
+    end
+
+    describe '#remove_repository' do
+      it 'returns true when the command succeeds' do
+        expect(Gitlab::Popen).to receive(:popen)
+          .with([projects_path, 'rm-project', 'current/storage', 'project/path.git'],
+                nil, popen_vars).and_return([nil, 0])
+
+        expect(gitlab_shell.remove_repository('current/storage', 'project/path')).to be true
+      end
+
+      it 'returns false when the command fails' do
+        expect(Gitlab::Popen).to receive(:popen)
+          .with([projects_path, 'rm-project', 'current/storage', 'project/path.git'],
+                nil, popen_vars).and_return(["error", 1])
+
+        expect(gitlab_shell.remove_repository('current/storage', 'project/path')).to be false
+      end
+    end
+
+    describe '#mv_repository' do
+      it 'returns true when the command succeeds' do
+        expect(Gitlab::Popen).to receive(:popen)
+          .with([projects_path, 'mv-project', 'current/storage', 'project/path.git', 'project/newpath.git'],
+                nil, popen_vars).and_return([nil, 0])
+
+        expect(gitlab_shell.mv_repository('current/storage', 'project/path', 'project/newpath')).to be true
+      end
+
+      it 'returns false when the command fails' do
+        expect(Gitlab::Popen).to receive(:popen)
+          .with([projects_path, 'mv-project', 'current/storage', 'project/path.git', 'project/newpath.git'],
+                nil, popen_vars).and_return(["error", 1])
+
+        expect(gitlab_shell.mv_repository('current/storage', 'project/path', 'project/newpath')).to be false
+      end
+    end
+
+    describe '#fork_repository' do
+      it 'returns true when the command succeeds' do
+        expect(Gitlab::Popen).to receive(:popen)
+          .with([projects_path, 'fork-project', 'current/storage', 'project/path.git', 'new/storage', 'new-namespace'],
+                nil, popen_vars).and_return([nil, 0])
+
+        expect(gitlab_shell.fork_repository('current/storage', 'project/path', 'new/storage', 'new-namespace')).to be true
+      end
+
+      it 'return false when the command fails' do
+        expect(Gitlab::Popen).to receive(:popen)
+          .with([projects_path, 'fork-project', 'current/storage', 'project/path.git', 'new/storage', 'new-namespace'],
+                nil, popen_vars).and_return(["error", 1])
+
+        expect(gitlab_shell.fork_repository('current/storage', 'project/path', 'new/storage', 'new-namespace')).to be false
+      end
+    end
+
     describe '#fetch_remote' do
       it 'returns true when the command succeeds' do
         expect(Gitlab::Popen).to receive(:popen)
-          .with([projects_path, 'fetch-remote', 'current/storage', 'project/path.git', 'new/storage', '800']).and_return([nil, 0])
+          .with([projects_path, 'fetch-remote', 'current/storage', 'project/path.git', 'new/storage', '800'],
+                nil, popen_vars).and_return([nil, 0])
 
         expect(gitlab_shell.fetch_remote('current/storage', 'project/path', 'new/storage')).to be true
       end
 
       it 'raises an exception when the command fails' do
         expect(Gitlab::Popen).to receive(:popen)
-        .with([projects_path, 'fetch-remote', 'current/storage', 'project/path.git', 'new/storage', '800']).and_return(["error", 1])
+        .with([projects_path, 'fetch-remote', 'current/storage', 'project/path.git', 'new/storage', '800'],
+              nil, popen_vars).and_return(["error", 1])
 
         expect { gitlab_shell.fetch_remote('current/storage', 'project/path', 'new/storage') }.to raise_error(Gitlab::Shell::Error, "error")
       end
@@ -119,14 +194,16 @@ describe Gitlab::Shell, lib: true do
     describe '#import_repository' do
       it 'returns true when the command succeeds' do
         expect(Gitlab::Popen).to receive(:popen)
-          .with([projects_path, 'import-project', 'current/storage', 'project/path.git', 'https://gitlab.com/gitlab-org/gitlab-ce.git', "800"]).and_return([nil, 0])
+          .with([projects_path, 'import-project', 'current/storage', 'project/path.git', 'https://gitlab.com/gitlab-org/gitlab-ce.git', "800"],
+                nil, popen_vars).and_return([nil, 0])
 
         expect(gitlab_shell.import_repository('current/storage', 'project/path', 'https://gitlab.com/gitlab-org/gitlab-ce.git')).to be true
       end
 
       it 'raises an exception when the command fails' do
         expect(Gitlab::Popen).to receive(:popen)
-        .with([projects_path, 'import-project', 'current/storage', 'project/path.git', 'https://gitlab.com/gitlab-org/gitlab-ce.git', "800"]).and_return(["error", 1])
+        .with([projects_path, 'import-project', 'current/storage', 'project/path.git', 'https://gitlab.com/gitlab-org/gitlab-ce.git', "800"],
+              nil, popen_vars).and_return(["error", 1])
 
         expect { gitlab_shell.import_repository('current/storage', 'project/path', 'https://gitlab.com/gitlab-org/gitlab-ce.git') }.to raise_error(Gitlab::Shell::Error, "error")
       end
diff --git a/spec/lib/gitlab/sherlock/file_sample_spec.rb b/spec/lib/gitlab/sherlock/file_sample_spec.rb
index cadf8bbce78057558264b159703a2d95517227bc..4989d14def30a83e641092039fb5f441eaa93c6e 100644
--- a/spec/lib/gitlab/sherlock/file_sample_spec.rb
+++ b/spec/lib/gitlab/sherlock/file_sample_spec.rb
@@ -35,8 +35,8 @@ describe Gitlab::Sherlock::FileSample, lib: true do
 
   describe '#relative_path' do
     it 'returns the relative path' do
-      expect(sample.relative_path).
-        to eq('spec/lib/gitlab/sherlock/file_sample_spec.rb')
+      expect(sample.relative_path)
+        .to eq('spec/lib/gitlab/sherlock/file_sample_spec.rb')
     end
   end
 
diff --git a/spec/lib/gitlab/sherlock/line_profiler_spec.rb b/spec/lib/gitlab/sherlock/line_profiler_spec.rb
index d57627bba2b4ab885d793a8313067fae8e21b816..39c6b2a48445da4d396f758cbc507f1b390d156a 100644
--- a/spec/lib/gitlab/sherlock/line_profiler_spec.rb
+++ b/spec/lib/gitlab/sherlock/line_profiler_spec.rb
@@ -20,9 +20,9 @@ describe Gitlab::Sherlock::LineProfiler, lib: true do
 
   describe '#profile_mri' do
     it 'returns an Array containing the return value and profiling samples' do
-      allow(profiler).to receive(:lineprof).
-        and_yield.
-        and_return({ __FILE__ => [[0, 0, 0, 0]] })
+      allow(profiler).to receive(:lineprof)
+        .and_yield
+        .and_return({ __FILE__ => [[0, 0, 0, 0]] })
 
       retval, samples = profiler.profile_mri { 42 }
 
diff --git a/spec/lib/gitlab/sherlock/middleware_spec.rb b/spec/lib/gitlab/sherlock/middleware_spec.rb
index 2bbeb25ce98341bbaa68d16b9615f9e89c557ac4..b98ab0b14a2abe3a69a665d3a2e9c947a26b15a8 100644
--- a/spec/lib/gitlab/sherlock/middleware_spec.rb
+++ b/spec/lib/gitlab/sherlock/middleware_spec.rb
@@ -72,8 +72,8 @@ describe Gitlab::Sherlock::Middleware, lib: true do
         'REQUEST_URI' => '/cats'
       }
 
-      expect(middleware.transaction_from_env(env)).
-        to be_an_instance_of(Gitlab::Sherlock::Transaction)
+      expect(middleware.transaction_from_env(env))
+        .to be_an_instance_of(Gitlab::Sherlock::Transaction)
     end
   end
 end
diff --git a/spec/lib/gitlab/sherlock/query_spec.rb b/spec/lib/gitlab/sherlock/query_spec.rb
index 0a62042813883267a2952d42897be0ccea68d868..d97b5eef573930d5288834827b1cd4e807dd5d5c 100644
--- a/spec/lib/gitlab/sherlock/query_spec.rb
+++ b/spec/lib/gitlab/sherlock/query_spec.rb
@@ -13,8 +13,8 @@ describe Gitlab::Sherlock::Query, lib: true do
       sql = 'SELECT COUNT(*) FROM users WHERE id = $1'
       bindings = [[double(:column), 10]]
 
-      query = described_class.
-        new_with_bindings(sql, bindings, started_at, finished_at)
+      query = described_class
+        .new_with_bindings(sql, bindings, started_at, finished_at)
 
       expect(query.query).to eq('SELECT COUNT(*) FROM users WHERE id = 10;')
     end
diff --git a/spec/lib/gitlab/sherlock/transaction_spec.rb b/spec/lib/gitlab/sherlock/transaction_spec.rb
index 9fe18f253f0bc3dea82f887f6cc7bf1769c87b9a..6ae1aa20ea720d12ad1d53e28d38a68b5f555e6d 100644
--- a/spec/lib/gitlab/sherlock/transaction_spec.rb
+++ b/spec/lib/gitlab/sherlock/transaction_spec.rb
@@ -109,8 +109,8 @@ describe Gitlab::Sherlock::Transaction, lib: true do
 
       query1 = Gitlab::Sherlock::Query.new('SELECT 1', start_time, start_time)
 
-      query2 = Gitlab::Sherlock::Query.
-        new('SELECT 2', start_time, start_time + 5)
+      query2 = Gitlab::Sherlock::Query
+        .new('SELECT 2', start_time, start_time + 5)
 
       transaction.queries << query1
       transaction.queries << query2
@@ -162,11 +162,11 @@ describe Gitlab::Sherlock::Transaction, lib: true do
   describe '#profile_lines' do
     describe 'when line profiling is enabled' do
       it 'yields the block using the line profiler' do
-        allow(Gitlab::Sherlock).to receive(:enable_line_profiler?).
-          and_return(true)
+        allow(Gitlab::Sherlock).to receive(:enable_line_profiler?)
+          .and_return(true)
 
-        allow_any_instance_of(Gitlab::Sherlock::LineProfiler).
-          to receive(:profile).and_return('cats are amazing', [])
+        allow_any_instance_of(Gitlab::Sherlock::LineProfiler)
+          .to receive(:profile).and_return('cats are amazing', [])
 
         retval = transaction.profile_lines { 'cats are amazing' }
 
@@ -176,8 +176,8 @@ describe Gitlab::Sherlock::Transaction, lib: true do
 
     describe 'when line profiling is disabled' do
       it 'yields the block' do
-        allow(Gitlab::Sherlock).to receive(:enable_line_profiler?).
-          and_return(false)
+        allow(Gitlab::Sherlock).to receive(:enable_line_profiler?)
+          .and_return(false)
 
         retval = transaction.profile_lines { 'cats are amazing' }
 
@@ -196,8 +196,8 @@ describe Gitlab::Sherlock::Transaction, lib: true do
     end
 
     it 'tracks executed queries' do
-      expect(transaction).to receive(:track_query).
-        with('SELECT 1', [], time, time)
+      expect(transaction).to receive(:track_query)
+        .with('SELECT 1', [], time, time)
 
       subscription.publish('test', time, time, nil, query_data)
     end
@@ -205,8 +205,8 @@ describe Gitlab::Sherlock::Transaction, lib: true do
     it 'only tracks queries triggered from the transaction thread' do
       expect(transaction).not_to receive(:track_query)
 
-      Thread.new { subscription.publish('test', time, time, nil, query_data) }.
-        join
+      Thread.new { subscription.publish('test', time, time, nil, query_data) }
+        .join
     end
   end
 
@@ -228,8 +228,8 @@ describe Gitlab::Sherlock::Transaction, lib: true do
     it 'only tracks views rendered from the transaction thread' do
       expect(transaction).not_to receive(:track_view)
 
-      Thread.new { subscription.publish('test', time, time, nil, view_data) }.
-        join
+      Thread.new { subscription.publish('test', time, time, nil, view_data) }
+        .join
     end
   end
 end
diff --git a/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb b/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb
index 6307f8c16a3f3b28927a576f3294aad96b40aef6..37d9e1d3e6b0426ab722aa9249227b17c462c1bd 100644
--- a/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb
+++ b/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb
@@ -5,8 +5,8 @@ describe Gitlab::SidekiqStatus::ClientMiddleware do
     it 'tracks the job in Redis' do
       expect(Gitlab::SidekiqStatus).to receive(:set).with('123', Gitlab::SidekiqStatus::DEFAULT_EXPIRATION)
 
-      described_class.new.
-        call('Foo', { 'jid' => '123' }, double(:queue), double(:pool)) { nil }
+      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
index 80728197b8cf313fe960f880ca69f498a7499f12..04e09d3dec81befa74414ae974c055f5aa823f73 100644
--- a/spec/lib/gitlab/sidekiq_status/server_middleware_spec.rb
+++ b/spec/lib/gitlab/sidekiq_status/server_middleware_spec.rb
@@ -5,8 +5,8 @@ describe Gitlab::SidekiqStatus::ServerMiddleware 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 }
+      ret = described_class.new
+        .call(double(:worker), { 'jid' => '123' }, double(:queue)) { 10 }
 
       expect(ret).to eq(10)
     end
diff --git a/spec/lib/gitlab/chat_commands/command_spec.rb b/spec/lib/gitlab/slash_commands/command_spec.rb
similarity index 93%
rename from spec/lib/gitlab/chat_commands/command_spec.rb
rename to spec/lib/gitlab/slash_commands/command_spec.rb
index 13e6953147bca8246bd4f9d8241c8946ca7b25ca..28d7f9858c33b858be16bcd81b4d344ad9683e02 100644
--- a/spec/lib/gitlab/chat_commands/command_spec.rb
+++ b/spec/lib/gitlab/slash_commands/command_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::ChatCommands::Command, service: true do
+describe Gitlab::SlashCommands::Command, service: true do
   let(:project) { create(:empty_project) }
   let(:user) { create(:user) }
 
@@ -93,19 +93,19 @@ describe Gitlab::ChatCommands::Command, service: true do
     context 'IssueShow is triggered' do
       let(:params) { { text: 'issue show 123' } }
 
-      it { is_expected.to eq(Gitlab::ChatCommands::IssueShow) }
+      it { is_expected.to eq(Gitlab::SlashCommands::IssueShow) }
     end
 
     context 'IssueCreate is triggered' do
       let(:params) { { text: 'issue create my title' } }
 
-      it { is_expected.to eq(Gitlab::ChatCommands::IssueNew) }
+      it { is_expected.to eq(Gitlab::SlashCommands::IssueNew) }
     end
 
     context 'IssueSearch is triggered' do
       let(:params) { { text: 'issue search my query' } }
 
-      it { is_expected.to eq(Gitlab::ChatCommands::IssueSearch) }
+      it { is_expected.to eq(Gitlab::SlashCommands::IssueSearch) }
     end
   end
 end
diff --git a/spec/lib/gitlab/chat_commands/deploy_spec.rb b/spec/lib/gitlab/slash_commands/deploy_spec.rb
similarity index 98%
rename from spec/lib/gitlab/chat_commands/deploy_spec.rb
rename to spec/lib/gitlab/slash_commands/deploy_spec.rb
index 46dbdeae37cea2475caca685462c01da3eef5814..d919f7260dbf3621b08de5e64392667d61d275ab 100644
--- a/spec/lib/gitlab/chat_commands/deploy_spec.rb
+++ b/spec/lib/gitlab/slash_commands/deploy_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::ChatCommands::Deploy, service: true do
+describe Gitlab::SlashCommands::Deploy, service: true do
   describe '#execute' do
     let(:project) { create(:empty_project) }
     let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/chat_commands/issue_new_spec.rb b/spec/lib/gitlab/slash_commands/issue_new_spec.rb
similarity index 97%
rename from spec/lib/gitlab/chat_commands/issue_new_spec.rb
rename to spec/lib/gitlab/slash_commands/issue_new_spec.rb
index 84c22328064e7f6390ba5e6361e315aa34c3f3bc..4de50d4a8bb638fe9406d68f54b8a9703f2446ea 100644
--- a/spec/lib/gitlab/chat_commands/issue_new_spec.rb
+++ b/spec/lib/gitlab/slash_commands/issue_new_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::ChatCommands::IssueNew, service: true do
+describe Gitlab::SlashCommands::IssueNew, service: true do
   describe '#execute' do
     let(:project) { create(:empty_project) }
     let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/chat_commands/issue_search_spec.rb b/spec/lib/gitlab/slash_commands/issue_search_spec.rb
similarity index 95%
rename from spec/lib/gitlab/chat_commands/issue_search_spec.rb
rename to spec/lib/gitlab/slash_commands/issue_search_spec.rb
index 551ccb79a5866f13176dc95d335511445123e002..06fff0afc50333fc5a95cba777d3e89536362ceb 100644
--- a/spec/lib/gitlab/chat_commands/issue_search_spec.rb
+++ b/spec/lib/gitlab/slash_commands/issue_search_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::ChatCommands::IssueSearch, service: true do
+describe Gitlab::SlashCommands::IssueSearch, service: true do
   describe '#execute' do
     let!(:issue) { create(:issue, project: project, title: 'find me') }
     let!(:confidential) { create(:issue, :confidential, project: project, title: 'mepmep find') }
diff --git a/spec/lib/gitlab/chat_commands/issue_show_spec.rb b/spec/lib/gitlab/slash_commands/issue_show_spec.rb
similarity index 96%
rename from spec/lib/gitlab/chat_commands/issue_show_spec.rb
rename to spec/lib/gitlab/slash_commands/issue_show_spec.rb
index 1f20d0a44ce56eb9c1d5af108c15b8a2c7a7b46e..1899f664ccd32f23612a274f15ec59c6110371b5 100644
--- a/spec/lib/gitlab/chat_commands/issue_show_spec.rb
+++ b/spec/lib/gitlab/slash_commands/issue_show_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::ChatCommands::IssueShow, service: true do
+describe Gitlab::SlashCommands::IssueShow, service: true do
   describe '#execute' do
     let(:issue) { create(:issue, project: project) }
     let(:project) { create(:empty_project) }
diff --git a/spec/lib/gitlab/chat_commands/presenters/access_spec.rb b/spec/lib/gitlab/slash_commands/presenters/access_spec.rb
similarity index 95%
rename from spec/lib/gitlab/chat_commands/presenters/access_spec.rb
rename to spec/lib/gitlab/slash_commands/presenters/access_spec.rb
index ae41d75ab0c2654df66e2f946d7a43ad0c6bf5f9..ef3d217f7bec159ab8fe92f9b4136780a1e01850 100644
--- a/spec/lib/gitlab/chat_commands/presenters/access_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/access_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::ChatCommands::Presenters::Access do
+describe Gitlab::SlashCommands::Presenters::Access do
   describe '#access_denied' do
     subject { described_class.new.access_denied }
 
diff --git a/spec/lib/gitlab/chat_commands/presenters/deploy_spec.rb b/spec/lib/gitlab/slash_commands/presenters/deploy_spec.rb
similarity index 96%
rename from spec/lib/gitlab/chat_commands/presenters/deploy_spec.rb
rename to spec/lib/gitlab/slash_commands/presenters/deploy_spec.rb
index dc2dd300072f5bd5c63275ad4658e1e7356900c4..dee3c77db2760ca871c632e490857d506a6308d6 100644
--- a/spec/lib/gitlab/chat_commands/presenters/deploy_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/deploy_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::ChatCommands::Presenters::Deploy do
+describe Gitlab::SlashCommands::Presenters::Deploy do
   let(:build) { create(:ci_build) }
 
   describe '#present' do
diff --git a/spec/lib/gitlab/chat_commands/presenters/issue_new_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb
similarity index 88%
rename from spec/lib/gitlab/chat_commands/presenters/issue_new_spec.rb
rename to spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb
index 17fcdbc2452c69700d310fe1c7dbf86acfacdf4a..7f81ebb47dbfc487fef04dcadbb0408807b1ff69 100644
--- a/spec/lib/gitlab/chat_commands/presenters/issue_new_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::ChatCommands::Presenters::IssueNew do
+describe Gitlab::SlashCommands::Presenters::IssueNew do
   let(:project) { create(:empty_project) }
   let(:issue) { create(:issue, project: project) }
   let(:attachment) { subject[:attachments].first }
diff --git a/spec/lib/gitlab/chat_commands/presenters/issue_search_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_search_spec.rb
similarity index 90%
rename from spec/lib/gitlab/chat_commands/presenters/issue_search_spec.rb
rename to spec/lib/gitlab/slash_commands/presenters/issue_search_spec.rb
index 3799a324db4a43a5d8d59e0d403f80d030f69708..7e57a0addcb10a4a97b5fa5d4294b7e163304be0 100644
--- a/spec/lib/gitlab/chat_commands/presenters/issue_search_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_search_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::ChatCommands::Presenters::IssueSearch do
+describe Gitlab::SlashCommands::Presenters::IssueSearch do
   let(:project) { create(:empty_project) }
   let(:message) { subject[:text] }
 
diff --git a/spec/lib/gitlab/chat_commands/presenters/issue_show_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb
similarity index 96%
rename from spec/lib/gitlab/chat_commands/presenters/issue_show_spec.rb
rename to spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb
index 3916fc704a4d91ab57eae93db1b48172bed6bba6..2a6ed86073741dc6b39562f0b5d8af3d12b68a23 100644
--- a/spec/lib/gitlab/chat_commands/presenters/issue_show_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::ChatCommands::Presenters::IssueShow do
+describe Gitlab::SlashCommands::Presenters::IssueShow do
   let(:project) { create(:empty_project) }
   let(:issue) { create(:issue, project: project) }
   let(:attachment) { subject[:attachments].first }
diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb
index e8a37e8d77be6355f7a420cd3d2e2abe53ccd19b..e9a6e2735169d052160bc17a8107d9a11eb3176c 100644
--- a/spec/lib/gitlab/url_builder_spec.rb
+++ b/spec/lib/gitlab/url_builder_spec.rb
@@ -112,8 +112,8 @@ describe Gitlab::UrlBuilder, lib: true do
         it 'returns a proper URL' do
           project = build_stubbed(:empty_project)
 
-          expect { described_class.build(project) }.
-            to raise_error(NotImplementedError, 'No URL builder defined for Project')
+          expect { described_class.build(project) }
+            .to raise_error(NotImplementedError, 'No URL builder defined for Project')
         end
       end
     end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index b47e1b56fa9c668ddfc1a4e280a70839d8e75260..c67188270286430b9171454727b2c3b6416923b3 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -30,13 +30,15 @@ describe Gitlab::UsageData do
       expect(count_data.keys).to match_array(%i(
         boards
         ci_builds
-        ci_pipelines
+        ci_internal_pipelines
+        ci_external_pipelines
         ci_runners
         ci_triggers
         ci_pipeline_schedules
         deploy_keys
         deployments
         environments
+        in_review_folder
         groups
         issues
         keys
diff --git a/spec/lib/gitlab/view/presenter/delegated_spec.rb b/spec/lib/gitlab/view/presenter/delegated_spec.rb
index e9d4af54389aac41a73237ea89b0b9d3709265b6..940a2ce6ebd9fa7d5894b15c88acb7fcb360c0df 100644
--- a/spec/lib/gitlab/view/presenter/delegated_spec.rb
+++ b/spec/lib/gitlab/view/presenter/delegated_spec.rb
@@ -18,8 +18,8 @@ describe Gitlab::View::Presenter::Delegated do
     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
+      expect { presenter_class.new(project, user: 'Jane Doe') }
+        .to raise_error Gitlab::View::Presenter::CannotOverrideMethodError
     end
   end
 
diff --git a/spec/lib/gitlab/visibility_level_spec.rb b/spec/lib/gitlab/visibility_level_spec.rb
index 3255c6f1ef73dc762d723a1a5e13c297db4d55f7..db9d2807be6161563e3b21aedc34d53bfad26e6f 100644
--- a/spec/lib/gitlab/visibility_level_spec.rb
+++ b/spec/lib/gitlab/visibility_level_spec.rb
@@ -18,4 +18,35 @@ describe Gitlab::VisibilityLevel, lib: true do
       expect(described_class.level_value(100)).to eq(Gitlab::VisibilityLevel::PRIVATE)
     end
   end
+
+  describe '.levels_for_user' do
+    it 'returns all levels for an admin' do
+      user = build(:user, :admin)
+
+      expect(described_class.levels_for_user(user))
+        .to eq([Gitlab::VisibilityLevel::PRIVATE,
+                Gitlab::VisibilityLevel::INTERNAL,
+                Gitlab::VisibilityLevel::PUBLIC])
+    end
+
+    it 'returns INTERNAL and PUBLIC for internal users' do
+      user = build(:user)
+
+      expect(described_class.levels_for_user(user))
+        .to eq([Gitlab::VisibilityLevel::INTERNAL,
+                Gitlab::VisibilityLevel::PUBLIC])
+    end
+
+    it 'returns PUBLIC for external users' do
+      user = build(:user, :external)
+
+      expect(described_class.levels_for_user(user))
+        .to eq([Gitlab::VisibilityLevel::PUBLIC])
+    end
+
+    it 'returns PUBLIC when no user is given' do
+      expect(described_class.levels_for_user)
+        .to eq([Gitlab::VisibilityLevel::PUBLIC])
+    end
+  end
 end
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index ad19998dff4a015320a18f620d1e2a28c6914806..493ff3bb5fbc3605267f95208267acb7bc096cbb 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -202,7 +202,11 @@ describe Gitlab::Workhorse, lib: true do
     context 'when Gitaly is enabled' do
       let(:gitaly_params) do
         {
-          GitalyAddress: Gitlab::GitalyClient.address('default')
+          GitalyAddress: Gitlab::GitalyClient.address('default'),
+          GitalyServer: {
+            address: Gitlab::GitalyClient.address('default'),
+            token: Gitlab::GitalyClient.token('default')
+          }
         }
       end
 
@@ -212,7 +216,6 @@ describe Gitlab::Workhorse, lib: true do
 
       it 'includes a Repository param' do
         repo_param = { Repository: {
-          path: '', # deprecated field; grpc automatically creates it anyway
           storage_name: 'default',
           relative_path: project.full_path + '.git'
         } }
diff --git a/spec/lib/mattermost/command_spec.rb b/spec/lib/mattermost/command_spec.rb
index 4b5938edeb9a6f681083933eed36efdaf18e7895..369e7b181b9cc7d237f826e16e429d1667f067d2 100644
--- a/spec/lib/mattermost/command_spec.rb
+++ b/spec/lib/mattermost/command_spec.rb
@@ -6,8 +6,8 @@ describe Mattermost::Command do
   before do
     Mattermost::Session.base_uri('http://mattermost.example.com')
 
-    allow_any_instance_of(Mattermost::Client).to receive(:with_session).
-      and_yield(Mattermost::Session.new(nil))
+    allow_any_instance_of(Mattermost::Client).to receive(:with_session)
+      .and_yield(Mattermost::Session.new(nil))
   end
 
   describe '#create' do
@@ -20,12 +20,12 @@ describe Mattermost::Command do
 
     context 'for valid trigger word' do
       before do
-        stub_request(:post, 'http://mattermost.example.com/api/v3/teams/abc/commands/create').
-          with(body: {
+        stub_request(:post, 'http://mattermost.example.com/api/v3/teams/abc/commands/create')
+          .with(body: {
             team_id: 'abc',
             trigger: 'gitlab'
-          }.to_json).
-          to_return(
+          }.to_json)
+          .to_return(
             status: 200,
             headers: { 'Content-Type' => 'application/json' },
             body: { token: 'token' }.to_json
@@ -39,8 +39,8 @@ describe Mattermost::Command do
 
     context 'for error message' do
       before do
-        stub_request(:post, 'http://mattermost.example.com/api/v3/teams/abc/commands/create').
-          to_return(
+        stub_request(:post, 'http://mattermost.example.com/api/v3/teams/abc/commands/create')
+          .to_return(
             status: 500,
             headers: { 'Content-Type' => 'application/json' },
             body: {
diff --git a/spec/lib/mattermost/session_spec.rb b/spec/lib/mattermost/session_spec.rb
index 74d12e3718105f58afdc828b83a3196220d4c0fd..be3908e8f6ab4b33fe9018e27ce27a7fe2e593b2 100644
--- a/spec/lib/mattermost/session_spec.rb
+++ b/spec/lib/mattermost/session_spec.rb
@@ -21,8 +21,8 @@ describe Mattermost::Session, type: :request do
   describe '#with session' do
     let(:location) { 'http://location.tld' }
     let!(:stub) do
-      WebMock.stub_request(:get, "#{mattermost_url}/api/v3/oauth/gitlab/login").
-        to_return(headers: { 'location' => location }, status: 307)
+      WebMock.stub_request(:get, "#{mattermost_url}/api/v3/oauth/gitlab/login")
+        .to_return(headers: { 'location' => location }, status: 307)
     end
 
     context 'without oauth uri' do
@@ -60,9 +60,9 @@ describe Mattermost::Session, type: :request do
         end
 
         before do
-          WebMock.stub_request(:get, "#{mattermost_url}/signup/gitlab/complete").
-            with(query: hash_including({ 'state' => state })).
-            to_return do |request|
+          WebMock.stub_request(:get, "#{mattermost_url}/signup/gitlab/complete")
+            .with(query: hash_including({ 'state' => state }))
+            .to_return do |request|
               post "/oauth/token",
                 client_id: doorkeeper.uid,
                 client_secret: doorkeeper.secret,
@@ -75,8 +75,8 @@ describe Mattermost::Session, type: :request do
               end
             end
 
-          WebMock.stub_request(:post, "#{mattermost_url}/api/v3/users/logout").
-            to_return(headers: { Authorization: 'token thisworksnow' }, status: 200)
+          WebMock.stub_request(:post, "#{mattermost_url}/api/v3/users/logout")
+            .to_return(headers: { Authorization: 'token thisworksnow' }, status: 200)
         end
 
         it 'can setup a session' do
diff --git a/spec/lib/mattermost/team_spec.rb b/spec/lib/mattermost/team_spec.rb
index ac493fdb20f7aa224bbb14f9cf5574f4afdebc56..e638ad7a2c9413a54a8728b9f0072dae987fcf45 100644
--- a/spec/lib/mattermost/team_spec.rb
+++ b/spec/lib/mattermost/team_spec.rb
@@ -4,8 +4,8 @@ describe Mattermost::Team do
   before do
     Mattermost::Session.base_uri('http://mattermost.example.com')
 
-    allow_any_instance_of(Mattermost::Client).to receive(:with_session).
-      and_yield(Mattermost::Session.new(nil))
+    allow_any_instance_of(Mattermost::Client).to receive(:with_session)
+      .and_yield(Mattermost::Session.new(nil))
   end
 
   describe '#all' do
@@ -30,8 +30,8 @@ describe Mattermost::Team do
       end
 
       before do
-        stub_request(:get, 'http://mattermost.example.com/api/v3/teams/all').
-          to_return(
+        stub_request(:get, 'http://mattermost.example.com/api/v3/teams/all')
+          .to_return(
             status: 200,
             headers: { 'Content-Type' => 'application/json' },
             body: response.to_json
@@ -45,8 +45,8 @@ describe Mattermost::Team do
 
     context 'for error message' do
       before do
-        stub_request(:get, 'http://mattermost.example.com/api/v3/teams/all').
-          to_return(
+        stub_request(:get, 'http://mattermost.example.com/api/v3/teams/all')
+          .to_return(
             status: 500,
             headers: { 'Content-Type' => 'application/json' },
             body: {
diff --git a/spec/lib/system_check/simple_executor_spec.rb b/spec/lib/system_check/simple_executor_spec.rb
index a5c6170cd7de77eb1f3e7401fe44434c21617aa9..795f11ee1f8b612f2e5f9c3a1c817ae53eaade34 100644
--- a/spec/lib/system_check/simple_executor_spec.rb
+++ b/spec/lib/system_check/simple_executor_spec.rb
@@ -75,6 +75,24 @@ describe SystemCheck::SimpleExecutor, lib: true do
     end
   end
 
+  class BugousCheck < SystemCheck::BaseCheck
+    CustomError = Class.new(StandardError)
+    set_name 'my bugous check'
+
+    def check?
+      raise CustomError, 'omg'
+    end
+  end
+
+  before do
+    @rainbow = Rainbow.enabled
+    Rainbow.enabled = false
+  end
+
+  after do
+    Rainbow.enabled = @rainbow
+  end
+
   describe '#component' do
     it 'returns stored component name' do
       expect(subject.component).to eq('Test')
@@ -219,5 +237,11 @@ describe SystemCheck::SimpleExecutor, lib: true do
         end
       end
     end
+
+    context 'when there is an exception' do
+      it 'rescues the exception' do
+        expect{ subject.run_check(BugousCheck) }.not_to raise_exception
+      end
+    end
   end
 end
diff --git a/spec/mailers/abuse_report_mailer_spec.rb b/spec/mailers/abuse_report_mailer_spec.rb
index eb433c38873e7279d498c0841438e68f04f7f7ce..bda892083b377febc277987dd6bf2221379ff9c5 100644
--- a/spec/mailers/abuse_report_mailer_spec.rb
+++ b/spec/mailers/abuse_report_mailer_spec.rb
@@ -30,8 +30,8 @@ describe AbuseReportMailer do
       it 'returns early' do
         stub_application_setting(admin_notification_email: nil)
 
-        expect { described_class.notify(spy).deliver_now }.
-          not_to change { ActionMailer::Base.deliveries.count }
+        expect { described_class.notify(spy).deliver_now }
+          .not_to change { ActionMailer::Base.deliveries.count }
       end
     end
   end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 980b24370d072a650a1723af9c3fa3e9db8b288c..683e893968b7729e9483f417345484b8b6de03d3 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -52,7 +52,7 @@ describe Notify do
           it 'has the correct subject and body' do
             aggregate_failures do
               is_expected.to have_referable_subject(issue)
-              is_expected.to have_body_text(namespace_project_issue_path(project.namespace, project, issue))
+              is_expected.to have_body_text(project_issue_path(project, issue))
             end
           end
 
@@ -99,7 +99,7 @@ describe Notify do
               is_expected.to have_referable_subject(issue, reply: true)
               is_expected.to have_html_escaped_body_text(previous_assignee.name)
               is_expected.to have_html_escaped_body_text(assignee.name)
-              is_expected.to have_body_text(namespace_project_issue_path(project.namespace, project, issue))
+              is_expected.to have_body_text(project_issue_path(project, issue))
             end
           end
         end
@@ -125,7 +125,7 @@ describe Notify do
             aggregate_failures do
               is_expected.to have_referable_subject(issue, reply: true)
               is_expected.to have_body_text('foo, bar, and baz')
-              is_expected.to have_body_text(namespace_project_issue_path(project.namespace, project, issue))
+              is_expected.to have_body_text(project_issue_path(project, issue))
             end
           end
 
@@ -165,7 +165,7 @@ describe Notify do
               is_expected.to have_referable_subject(issue, reply: true)
               is_expected.to have_body_text(status)
               is_expected.to have_html_escaped_body_text(current_user.name)
-              is_expected.to have_body_text(namespace_project_issue_path project.namespace, project, issue)
+              is_expected.to have_body_text(project_issue_path project, issue)
             end
           end
         end
@@ -185,13 +185,12 @@ describe Notify do
           end
 
           it 'has the correct subject and body' do
-            new_issue_url = namespace_project_issue_path(new_issue.project.namespace,
-                                                         new_issue.project, new_issue)
+            new_issue_url = project_issue_path(new_issue.project, new_issue)
 
             aggregate_failures do
               is_expected.to have_referable_subject(issue, reply: true)
               is_expected.to have_body_text(new_issue_url)
-              is_expected.to have_body_text(namespace_project_issue_path(project.namespace, project, issue))
+              is_expected.to have_body_text(project_issue_path(project, issue))
             end
           end
         end
@@ -216,7 +215,7 @@ describe Notify do
           it 'has the correct subject and body' do
             aggregate_failures do
               is_expected.to have_referable_subject(merge_request)
-              is_expected.to have_body_text(namespace_project_merge_request_path(project.namespace, project, merge_request))
+              is_expected.to have_body_text(project_merge_request_path(project, merge_request))
               is_expected.to have_body_text(merge_request.source_branch)
               is_expected.to have_body_text(merge_request.target_branch)
             end
@@ -265,7 +264,7 @@ describe Notify do
             aggregate_failures do
               is_expected.to have_referable_subject(merge_request, reply: true)
               is_expected.to have_html_escaped_body_text(previous_assignee.name)
-              is_expected.to have_body_text(namespace_project_merge_request_path(project.namespace, project, merge_request))
+              is_expected.to have_body_text(project_merge_request_path(project, merge_request))
               is_expected.to have_html_escaped_body_text(assignee.name)
             end
           end
@@ -291,7 +290,7 @@ describe Notify do
           it 'has the correct subject and body' do
             is_expected.to have_referable_subject(merge_request, reply: true)
             is_expected.to have_body_text('foo, bar, and baz')
-            is_expected.to have_body_text(namespace_project_merge_request_path(project.namespace, project, merge_request))
+            is_expected.to have_body_text(project_merge_request_path(project, merge_request))
           end
         end
 
@@ -316,7 +315,7 @@ describe Notify do
               is_expected.to have_referable_subject(merge_request, reply: true)
               is_expected.to have_body_text(status)
               is_expected.to have_html_escaped_body_text(current_user.name)
-              is_expected.to have_body_text(namespace_project_merge_request_path(project.namespace, project, merge_request))
+              is_expected.to have_body_text(project_merge_request_path(project, merge_request))
             end
           end
         end
@@ -341,7 +340,7 @@ describe Notify do
             aggregate_failures do
               is_expected.to have_referable_subject(merge_request, reply: true)
               is_expected.to have_body_text('merged')
-              is_expected.to have_body_text(namespace_project_merge_request_path(project.namespace, project, merge_request))
+              is_expected.to have_body_text(project_merge_request_path(project, merge_request))
             end
           end
         end
@@ -390,7 +389,7 @@ describe Notify do
 
           is_expected.to have_subject "Request to join the #{project.name_with_namespace} project"
           is_expected.to have_html_escaped_body_text project.name_with_namespace
-          is_expected.to have_body_text namespace_project_project_members_url(project.namespace, project)
+          is_expected.to have_body_text project_project_members_url(project)
           is_expected.to have_body_text project_member.human_access
         end
       end
@@ -417,7 +416,7 @@ describe Notify do
 
           is_expected.to have_subject "Request to join the #{project.name_with_namespace} project"
           is_expected.to have_html_escaped_body_text project.name_with_namespace
-          is_expected.to have_body_text namespace_project_project_members_url(project.namespace, project)
+          is_expected.to have_body_text project_project_members_url(project)
           is_expected.to have_body_text project_member.human_access
         end
       end
@@ -609,7 +608,7 @@ describe Notify do
 
       describe 'on a merge request' do
         let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
-        let(:note_on_merge_request_path) { namespace_project_merge_request_path(project.namespace, project, merge_request, anchor: "note_#{note.id}") }
+        let(:note_on_merge_request_path) { project_merge_request_path(project, merge_request, anchor: "note_#{note.id}") }
 
         before do
           allow(note).to receive(:noteable).and_return(merge_request)
@@ -634,7 +633,7 @@ describe Notify do
 
       describe 'on an issue' do
         let(:issue) { create(:issue, project: project) }
-        let(:note_on_issue_path) { namespace_project_issue_path(project.namespace, project, issue, anchor: "note_#{note.id}") }
+        let(:note_on_issue_path) { project_issue_path(project, issue, anchor: "note_#{note.id}") }
 
         before do
           allow(note).to receive(:noteable).and_return(issue)
@@ -725,7 +724,7 @@ describe Notify do
       describe 'on a merge request' do
         let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
         let(:note) { create(:discussion_note_on_merge_request, noteable: merge_request, project: project, author: note_author) }
-        let(:note_on_merge_request_path) { namespace_project_merge_request_path(project.namespace, project, merge_request, anchor: "note_#{note.id}") }
+        let(:note_on_merge_request_path) { project_merge_request_path(project, merge_request, anchor: "note_#{note.id}") }
 
         before do
           allow(note).to receive(:noteable).and_return(merge_request)
@@ -752,7 +751,7 @@ describe Notify do
       describe 'on an issue' do
         let(:issue) { create(:issue, project: project) }
         let(:note) { create(:discussion_note_on_issue, noteable: issue, project: project, author: note_author) }
-        let(:note_on_issue_path) { namespace_project_issue_path(project.namespace, project, issue, anchor: "note_#{note.id}") }
+        let(:note_on_issue_path) { project_issue_path(project, issue, anchor: "note_#{note.id}") }
 
         before do
           allow(note).to receive(:noteable).and_return(issue)
@@ -1022,7 +1021,7 @@ describe Notify do
   describe 'email on push for a created branch' do
     let(:example_site_path) { root_path }
     let(:user) { create(:user) }
-    let(:tree_path) { namespace_project_tree_path(project.namespace, project, "empty-branch") }
+    let(:tree_path) { project_tree_path(project, "empty-branch") }
 
     subject { described_class.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/empty-branch', action: :create) }
 
@@ -1048,7 +1047,7 @@ describe Notify do
   describe 'email on push for a created tag' do
     let(:example_site_path) { root_path }
     let(:user) { create(:user) }
-    let(:tree_path) { namespace_project_tree_path(project.namespace, project, "v1.0") }
+    let(:tree_path) { project_tree_path(project, "v1.0") }
 
     subject { described_class.repository_push_email(project.id, author_id: user.id, ref: 'refs/tags/v1.0', action: :create) }
 
@@ -1122,7 +1121,7 @@ describe Notify do
     let(:raw_compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, sample_image_commit.id, sample_commit.id) }
     let(:compare) { Compare.decorate(raw_compare, project) }
     let(:commits) { compare.commits }
-    let(:diff_path) { namespace_project_compare_path(project.namespace, project, from: Commit.new(compare.base, project), to: Commit.new(compare.head, project)) }
+    let(:diff_path) { project_compare_path(project, from: Commit.new(compare.base, project), to: Commit.new(compare.head, project)) }
     let(:send_from_committer_email) { false }
     let(:diff_refs) { Gitlab::Diff::DiffRefs.new(base_sha: project.merge_base_commit(sample_image_commit.id, sample_commit.id).id, head_sha: sample_commit.id) }
 
@@ -1216,7 +1215,7 @@ describe Notify do
     let(:raw_compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, sample_commit.parent_id, sample_commit.id) }
     let(:compare) { Compare.decorate(raw_compare, project) }
     let(:commits) { compare.commits }
-    let(:diff_path) { namespace_project_commit_path(project.namespace, project, commits.first) }
+    let(:diff_path) { project_commit_path(project, commits.first) }
     let(:diff_refs) { Gitlab::Diff::DiffRefs.new(base_sha: project.merge_base_commit(sample_image_commit.id, sample_commit.id).id, head_sha: sample_commit.id) }
 
     subject { described_class.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare, diff_refs: diff_refs) }
diff --git a/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb b/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb
index bd5f85b901dde28df26e16c803b4669e05c4651f..65bea662b02a71d9f4155d528a6147fdb5a7a24f 100644
--- a/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb
+++ b/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 require Rails.root.join('db', 'post_migrate', '20170508170547_add_head_pipeline_for_each_merge_request.rb')
 
-describe AddHeadPipelineForEachMergeRequest do
+describe AddHeadPipelineForEachMergeRequest, :truncate do
   let(:migration) { described_class.new }
 
   let!(:project) { create(:empty_project) }
diff --git a/spec/migrations/migrate_build_stage_reference_spec.rb b/spec/migrations/migrate_build_stage_reference_again_spec.rb
similarity index 93%
rename from spec/migrations/migrate_build_stage_reference_spec.rb
rename to spec/migrations/migrate_build_stage_reference_again_spec.rb
index 80b321860c22c945e78970d11bd9fe13f100c9c3..6be480ce58efd5fe17be9ac1ba4a3055704d8fa7 100644
--- a/spec/migrations/migrate_build_stage_reference_spec.rb
+++ b/spec/migrations/migrate_build_stage_reference_again_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20170526185921_migrate_build_stage_reference.rb')
+require Rails.root.join('db', 'post_migrate', '20170526190000_migrate_build_stage_reference_again.rb')
 
-describe MigrateBuildStageReference, :migration do
+describe MigrateBuildStageReferenceAgain, :migration do
   ##
   # Create test data - pipeline and CI/CD jobs.
   #
diff --git a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb
index 3db57595fa6fae1d74c30689ffbd63899a1f30f0..4223d2337a80432552e8545cd7422805f13d53ef 100644
--- a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb
+++ b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb
@@ -11,33 +11,33 @@ describe MigrateProcessCommitWorkerJobs do
   describe 'Project' do
     describe 'find_including_path' do
       it 'returns Project instances' do
-        expect(described_class::Project.find_including_path(project.id)).
-          to be_an_instance_of(described_class::Project)
+        expect(described_class::Project.find_including_path(project.id))
+          .to be_an_instance_of(described_class::Project)
       end
 
       it 'selects the full path for every Project' do
-        migration_project = described_class::Project.
-          find_including_path(project.id)
+        migration_project = described_class::Project
+          .find_including_path(project.id)
 
-        expect(migration_project[:path_with_namespace]).
-          to eq(project.path_with_namespace)
+        expect(migration_project[:path_with_namespace])
+          .to eq(project.path_with_namespace)
       end
     end
 
     describe '#repository_storage_path' do
       it 'returns the storage path for the repository' do
-        migration_project = described_class::Project.
-          find_including_path(project.id)
+        migration_project = described_class::Project
+          .find_including_path(project.id)
 
-        expect(File.directory?(migration_project.repository_storage_path)).
-          to eq(true)
+        expect(File.directory?(migration_project.repository_storage_path))
+          .to eq(true)
       end
     end
 
     describe '#repository_path' do
       it 'returns the path to the repository' do
-        migration_project = described_class::Project.
-          find_including_path(project.id)
+        migration_project = described_class::Project
+          .find_including_path(project.id)
 
         expect(File.directory?(migration_project.repository_path)).to eq(true)
       end
@@ -45,11 +45,11 @@ describe MigrateProcessCommitWorkerJobs do
 
     describe '#repository' do
       it 'returns a Rugged::Repository' do
-        migration_project = described_class::Project.
-          find_including_path(project.id)
+        migration_project = described_class::Project
+          .find_including_path(project.id)
 
-        expect(migration_project.repository).
-          to be_an_instance_of(Rugged::Repository)
+        expect(migration_project.repository)
+          .to be_an_instance_of(Rugged::Repository)
       end
     end
   end
@@ -73,9 +73,9 @@ describe MigrateProcessCommitWorkerJobs do
     end
 
     it 'skips jobs using a project that no longer exists' do
-      allow(described_class::Project).to receive(:find_including_path).
-        with(project.id).
-        and_return(nil)
+      allow(described_class::Project).to receive(:find_including_path)
+        .with(project.id)
+        .and_return(nil)
 
       migration.up
 
@@ -83,9 +83,9 @@ describe MigrateProcessCommitWorkerJobs do
     end
 
     it 'skips jobs using commits that no longer exist' do
-      allow_any_instance_of(Rugged::Repository).to receive(:lookup).
-        with(commit.oid).
-        and_raise(Rugged::OdbError)
+      allow_any_instance_of(Rugged::Repository).to receive(:lookup)
+        .with(commit.oid)
+        .and_raise(Rugged::OdbError)
 
       migration.up
 
@@ -99,12 +99,12 @@ describe MigrateProcessCommitWorkerJobs do
     end
 
     it 'encodes data to UTF-8' do
-      allow_any_instance_of(Rugged::Repository).to receive(:lookup).
-        with(commit.oid).
-        and_return(commit)
+      allow_any_instance_of(Rugged::Repository).to receive(:lookup)
+        .with(commit.oid)
+        .and_return(commit)
 
-      allow(commit).to receive(:message).
-        and_return('김치'.force_encoding('BINARY'))
+      allow(commit).to receive(:message)
+        .and_return('김치'.force_encoding('BINARY'))
 
       migration.up
 
diff --git a/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb b/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb
index 1db9bc002ae91b486e6b52668d6c941d7215adef..e3b42b5eac8baee3e6dc1d64836c8d43f29e4459 100644
--- a/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb
+++ b/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb
@@ -3,7 +3,7 @@
 require 'spec_helper'
 require Rails.root.join('db', 'post_migrate', '20170324160416_migrate_user_activities_to_users_last_activity_on.rb')
 
-describe MigrateUserActivitiesToUsersLastActivityOn, :redis do
+describe MigrateUserActivitiesToUsersLastActivityOn, :redis, :truncate do
   let(:migration) { described_class.new }
   let!(:user_active_1) { create(:user) }
   let!(:user_active_2) { create(:user) }
diff --git a/spec/migrations/migrate_user_project_view_spec.rb b/spec/migrations/migrate_user_project_view_spec.rb
index 70f8e0d6082c65be53ae1ca593ce58672df23035..afaa5d836a77f1481f5f739b6afafaae55a130bc 100644
--- a/spec/migrations/migrate_user_project_view_spec.rb
+++ b/spec/migrations/migrate_user_project_view_spec.rb
@@ -3,7 +3,7 @@
 require 'spec_helper'
 require Rails.root.join('db', 'post_migrate', '20170406142253_migrate_user_project_view.rb')
 
-describe MigrateUserProjectView do
+describe MigrateUserProjectView, :truncate do
   let(:migration) { described_class.new }
   let!(:user) { create(:user) }
 
diff --git a/spec/migrations/rename_duplicated_variable_key_spec.rb b/spec/migrations/rename_duplicated_variable_key_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..11096564dfa7d5763afc9f917f172942ef0686ee
--- /dev/null
+++ b/spec/migrations/rename_duplicated_variable_key_spec.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20170622135451_rename_duplicated_variable_key.rb')
+
+describe RenameDuplicatedVariableKey, :migration do
+  let(:variables) { table(:ci_variables) }
+  let(:projects) { table(:projects) }
+
+  before do
+    projects.create!(id: 1)
+    variables.create!(id: 1, key: 'key1', project_id: 1)
+    variables.create!(id: 2, key: 'key2', project_id: 1)
+    variables.create!(id: 3, key: 'keyX', project_id: 1)
+    variables.create!(id: 4, key: 'keyX', project_id: 1)
+    variables.create!(id: 5, key: 'keyY', project_id: 1)
+    variables.create!(id: 6, key: 'keyX', project_id: 1)
+    variables.create!(id: 7, key: 'key7', project_id: 1)
+    variables.create!(id: 8, key: 'keyY', project_id: 1)
+  end
+
+  it 'correctly remove duplicated records with smaller id' do
+    migrate!
+
+    expect(variables.pluck(:id, :key)).to contain_exactly(
+      [1, 'key1'],
+      [2, 'key2'],
+      [3, 'keyX_3'],
+      [4, 'keyX_4'],
+      [5, 'keyY_5'],
+      [6, 'keyX'],
+      [7, 'key7'],
+      [8, 'keyY']
+    )
+  end
+end
diff --git a/spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb b/spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb
index 175bf1876b2c207f7a598650481647047ff3dbed..42109fd074396dd0e9d7f09014ea55a363c13ce1 100644
--- a/spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb
+++ b/spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb
@@ -31,8 +31,8 @@ describe TurnNestedGroupsIntoRegularGroupsForMysql do
     end
 
     it 'adds members of parent groups as members to the migrated group' do
-      is_member = child_group.members.
-        where(user_id: member, access_level: Gitlab::Access::DEVELOPER).any?
+      is_member = child_group.members
+        .where(user_id: member, access_level: Gitlab::Access::DEVELOPER).any?
 
       expect(is_member).to eq(true)
     end
@@ -44,21 +44,21 @@ describe TurnNestedGroupsIntoRegularGroupsForMysql do
     end
 
     it 'renames projects of the nested group' do
-      expect(updated_project.path_with_namespace).
-        to eq("#{parent_group.name}-#{child_group.name}/#{updated_project.path}")
+      expect(updated_project.path_with_namespace)
+        .to eq("#{parent_group.name}-#{child_group.name}/#{updated_project.path}")
     end
 
     it 'renames the repository of any projects' do
-      expect(updated_project.repository.path).
-        to end_with("#{parent_group.name}-#{child_group.name}/#{updated_project.path}.git")
+      expect(updated_project.repository.path)
+        .to end_with("#{parent_group.name}-#{child_group.name}/#{updated_project.path}.git")
 
       expect(File.directory?(updated_project.repository.path)).to eq(true)
     end
 
     it 'creates a redirect route for renamed projects' do
-      exists = RedirectRoute.
-        where(source_type: 'Project', source_id: project.id).
-        any?
+      exists = RedirectRoute
+        .where(source_type: 'Project', source_id: project.id)
+        .any?
 
       expect(exists).to eq(true)
     end
diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb
index 92d70cfc64c199f96a675de3314db0454792e569..dc7a0d80752b75cfed4c2dd815210ff8f1f5b61c 100644
--- a/spec/models/ability_spec.rb
+++ b/spec/models/ability_spec.rb
@@ -2,8 +2,8 @@ require 'spec_helper'
 
 describe Ability, lib: true do
   context 'using a nil subject' do
-    it 'is always empty' do
-      expect(Ability.allowed(nil, nil).to_set).to be_empty
+    it 'has no permissions' do
+      expect(Ability.policy_for(nil, nil)).to be_banned
     end
   end
 
@@ -69,8 +69,8 @@ describe Ability, lib: true do
         project = create(:empty_project, :public)
         user = build(:user)
 
-        expect(described_class.users_that_can_read_project([user], project)).
-          to eq([user])
+        expect(described_class.users_that_can_read_project([user], project))
+          .to eq([user])
       end
     end
 
@@ -80,8 +80,8 @@ describe Ability, lib: true do
       it 'returns users that are administrators' do
         user = build(:user, admin: true)
 
-        expect(described_class.users_that_can_read_project([user], project)).
-          to eq([user])
+        expect(described_class.users_that_can_read_project([user], project))
+          .to eq([user])
       end
 
       it 'returns internal users while skipping external users' do
@@ -89,8 +89,8 @@ describe Ability, lib: true do
         user2 = build(:user, external: true)
         users = [user1, user2]
 
-        expect(described_class.users_that_can_read_project(users, project)).
-          to eq([user1])
+        expect(described_class.users_that_can_read_project(users, project))
+          .to eq([user1])
       end
 
       it 'returns external users if they are the project owner' do
@@ -100,8 +100,8 @@ describe Ability, lib: true do
 
         expect(project).to receive(:owner).twice.and_return(user1)
 
-        expect(described_class.users_that_can_read_project(users, project)).
-          to eq([user1])
+        expect(described_class.users_that_can_read_project(users, project))
+          .to eq([user1])
       end
 
       it 'returns external users if they are project members' do
@@ -111,8 +111,8 @@ describe Ability, lib: true do
 
         expect(project.team).to receive(:members).twice.and_return([user1])
 
-        expect(described_class.users_that_can_read_project(users, project)).
-          to eq([user1])
+        expect(described_class.users_that_can_read_project(users, project))
+          .to eq([user1])
       end
 
       it 'returns an empty Array if all users are external users without access' do
@@ -120,8 +120,8 @@ describe Ability, lib: true do
         user2 = build(:user, external: true)
         users = [user1, user2]
 
-        expect(described_class.users_that_can_read_project(users, project)).
-          to eq([])
+        expect(described_class.users_that_can_read_project(users, project))
+          .to eq([])
       end
     end
 
@@ -131,8 +131,8 @@ describe Ability, lib: true do
       it 'returns users that are administrators' do
         user = build(:user, admin: true)
 
-        expect(described_class.users_that_can_read_project([user], project)).
-          to eq([user])
+        expect(described_class.users_that_can_read_project([user], project))
+          .to eq([user])
       end
 
       it 'returns external users if they are the project owner' do
@@ -142,8 +142,8 @@ describe Ability, lib: true do
 
         expect(project).to receive(:owner).twice.and_return(user1)
 
-        expect(described_class.users_that_can_read_project(users, project)).
-          to eq([user1])
+        expect(described_class.users_that_can_read_project(users, project))
+          .to eq([user1])
       end
 
       it 'returns external users if they are project members' do
@@ -153,8 +153,8 @@ describe Ability, lib: true do
 
         expect(project.team).to receive(:members).twice.and_return([user1])
 
-        expect(described_class.users_that_can_read_project(users, project)).
-          to eq([user1])
+        expect(described_class.users_that_can_read_project(users, project))
+          .to eq([user1])
       end
 
       it 'returns an empty Array if all users are internal users without access' do
@@ -162,8 +162,8 @@ describe Ability, lib: true do
         user2 = build(:user)
         users = [user1, user2]
 
-        expect(described_class.users_that_can_read_project(users, project)).
-          to eq([])
+        expect(described_class.users_that_can_read_project(users, project))
+          .to eq([])
       end
 
       it 'returns an empty Array if all users are external users without access' do
@@ -171,8 +171,8 @@ describe Ability, lib: true do
         user2 = build(:user, external: true)
         users = [user1, user2]
 
-        expect(described_class.users_that_can_read_project(users, project)).
-          to eq([])
+        expect(described_class.users_that_can_read_project(users, project))
+          .to eq([])
       end
     end
   end
@@ -210,8 +210,8 @@ describe Ability, lib: true do
         user = build(:user, admin: true)
         issue = build(:issue)
 
-        expect(described_class.issues_readable_by_user([issue], user)).
-          to eq([issue])
+        expect(described_class.issues_readable_by_user([issue], user))
+          .to eq([issue])
       end
     end
 
@@ -222,8 +222,8 @@ describe Ability, lib: true do
 
         expect(issue).to receive(:readable_by?).with(user).and_return(true)
 
-        expect(described_class.issues_readable_by_user([issue], user)).
-          to eq([issue])
+        expect(described_class.issues_readable_by_user([issue], user))
+          .to eq([issue])
       end
 
       it 'returns an empty Array when no issues are readable' do
@@ -244,8 +244,8 @@ describe Ability, lib: true do
         expect(hidden_issue).to receive(:publicly_visible?).and_return(false)
         expect(visible_issue).to receive(:publicly_visible?).and_return(true)
 
-        issues = described_class.
-          issues_readable_by_user([hidden_issue, visible_issue])
+        issues = described_class
+          .issues_readable_by_user([hidden_issue, visible_issue])
 
         expect(issues).to eq([visible_issue])
       end
@@ -255,12 +255,15 @@ describe Ability, lib: true do
   describe '.project_disabled_features_rules' do
     let(:project) { create(:empty_project, :wiki_disabled) }
 
-    subject { described_class.allowed(project.owner, project) }
+    subject { described_class.policy_for(project.owner, project) }
 
     context 'wiki named abilities' do
       it 'disables wiki abilities if the project has no wiki' do
         expect(project).to receive(:has_external_wiki?).and_return(false)
-        expect(subject).not_to include(:read_wiki, :create_wiki, :update_wiki, :admin_wiki)
+        expect(subject).not_to be_allowed(:read_wiki)
+        expect(subject).not_to be_allowed(:create_wiki)
+        expect(subject).not_to be_allowed(:update_wiki)
+        expect(subject).not_to be_allowed(:admin_wiki)
       end
     end
   end
diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb
index 90aec2b45e6870b399162357e11ee9f8e9934c55..c1bf5551fe032103bdbc3bb3bac9fb124e836614 100644
--- a/spec/models/abuse_report_spec.rb
+++ b/spec/models/abuse_report_spec.rb
@@ -36,8 +36,8 @@ RSpec.describe AbuseReport, type: :model do
 
   describe '#notify' do
     it 'delivers' do
-      expect(AbuseReportMailer).to receive(:notify).with(subject.id).
-        and_return(spy)
+      expect(AbuseReportMailer).to receive(:notify).with(subject.id)
+        .and_return(spy)
 
       subject.notify
     end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 3816422fec6e1b4fd4b0220ad25ca71475b2bbc7..a7ba3a7c43e0ab89fe8d49344bb3b513e1461239 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -451,42 +451,6 @@ describe Ci::Build, :models do
       end
     end
 
-    describe '#environment_url' do
-      subject { job.environment_url }
-
-      context 'when yaml environment uses $CI_COMMIT_REF_NAME' do
-        let(:job) do
-          create(:ci_build,
-                 ref: 'master',
-                 options: { environment: { url: 'http://review/$CI_COMMIT_REF_NAME' } })
-        end
-
-        it { is_expected.to eq('http://review/master') }
-      end
-
-      context 'when yaml environment uses yaml_variables containing symbol keys' do
-        let(:job) do
-          create(:ci_build,
-                 yaml_variables: [{ key: :APP_HOST, value: 'host' }],
-                 options: { environment: { url: 'http://review/$APP_HOST' } })
-        end
-
-        it { is_expected.to eq('http://review/host') }
-      end
-
-      context 'when yaml environment does not have url' do
-        let(:job) { create(:ci_build, environment: 'staging') }
-
-        let!(:environment) do
-          create(:environment, project: job.project, name: job.environment)
-        end
-
-        it 'returns the external_url from persisted environment' do
-          is_expected.to eq(environment.external_url)
-        end
-      end
-    end
-
     describe '#starts_environment?' do
       subject { build.starts_environment? }
 
@@ -899,8 +863,8 @@ describe Ci::Build, :models do
         pipeline2 = create(:ci_pipeline, project: project)
         @build2 = create(:ci_build, pipeline: pipeline2)
 
-        allow(@merge_request).to receive(:commits_sha).
-          and_return([pipeline.sha, pipeline2.sha])
+        allow(@merge_request).to receive(:commits_sha)
+          .and_return([pipeline.sha, pipeline2.sha])
         allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request])
       end
 
@@ -1034,13 +998,17 @@ describe Ci::Build, :models do
 
   describe '#ref_slug' do
     {
-      'master'    => 'master',
-      '1-foo'     => '1-foo',
-      'fix/1-foo' => 'fix-1-foo',
-      'fix-1-foo' => 'fix-1-foo',
-      'a' * 63    => 'a' * 63,
-      'a' * 64    => 'a' * 63,
-      'FOO'       => 'foo'
+      'master'                => 'master',
+      '1-foo'                 => '1-foo',
+      'fix/1-foo'             => 'fix-1-foo',
+      'fix-1-foo'             => 'fix-1-foo',
+      'a' * 63                => 'a' * 63,
+      'a' * 64                => 'a' * 63,
+      'FOO'                   => 'foo',
+      '-' + 'a' * 61 + '-'    => 'a' * 61,
+      '-' + 'a' * 62 + '-'    => 'a' * 62,
+      '-' + 'a' * 63 + '-'    => 'a' * 62,
+      'a' * 62 + ' '          => 'a' * 62
     }.each do |ref, slug|
       it "transforms #{ref} to #{slug}" do
         build.ref = ref
@@ -1215,6 +1183,7 @@ describe Ci::Build, :models do
         { key: 'CI_PROJECT_NAMESPACE', value: project.namespace.full_path, public: true },
         { key: 'CI_PROJECT_URL', value: project.web_url, public: true },
         { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true },
+        { key: 'CI_CONFIG_PATH', value: pipeline.ci_yaml_file_path, public: true },
         { key: 'CI_REGISTRY_USER', value: 'gitlab-ci-token', public: true },
         { key: 'CI_REGISTRY_PASSWORD', value: build.token, public: false },
         { key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false }
@@ -1292,10 +1261,20 @@ describe Ci::Build, :models do
 
         context 'when the URL was set from the job' do
           before do
-            build.update(options: { environment: { url: 'http://host/$CI_JOB_NAME' } })
+            build.update(options: { environment: { url: url } })
           end
 
           it_behaves_like 'containing environment variables'
+
+          context 'when variables are used in the URL, it does not expand' do
+            let(:url) { 'http://$CI_PROJECT_NAME-$CI_ENVIRONMENT_SLUG' }
+
+            it_behaves_like 'containing environment variables'
+
+            it 'puts $CI_ENVIRONMENT_URL in the last so all other variables are available to be used when runners are trying to expand it' do
+              expect(subject.last).to eq(environment_variables.last)
+            end
+          end
         end
 
         context 'when the URL was not set from the job, but environment' do
@@ -1495,6 +1474,16 @@ describe Ci::Build, :models do
       it { is_expected.to include(deployment_variable) }
     end
 
+    context 'when project has custom CI config path' do
+      let(:ci_config_path) { { key: 'CI_CONFIG_PATH', value: 'custom', public: true } }
+
+      before do
+        project.update(ci_config_path: 'custom')
+      end
+
+      it { is_expected.to include(ci_config_path) }
+    end
+
     context 'returns variables in valid order' do
       let(:build_pre_var) { { key: 'build', value: 'value' } }
       let(:project_pre_var) { { key: 'project', value: 'value' } }
diff --git a/spec/models/ci/pipeline_schedule_spec.rb b/spec/models/ci/pipeline_schedule_spec.rb
index b00e7a735712fb29396e7789e15e19d8bdd22651..56817baf79d65d679a0da8b1e9c729e2c6e0f7b5 100644
--- a/spec/models/ci/pipeline_schedule_spec.rb
+++ b/spec/models/ci/pipeline_schedule_spec.rb
@@ -40,8 +40,8 @@ describe Ci::PipelineSchedule, models: true do
 
     context 'when creates new pipeline schedule' do
       let(:expected_next_run_at) do
-        Gitlab::Ci::CronParser.new(pipeline_schedule.cron, pipeline_schedule.cron_timezone).
-          next_time_from(Time.now)
+        Gitlab::Ci::CronParser.new(pipeline_schedule.cron, pipeline_schedule.cron_timezone)
+          .next_time_from(Time.now)
       end
 
       it 'updates next_run_at automatically' do
@@ -53,8 +53,8 @@ describe Ci::PipelineSchedule, models: true do
       let(:new_cron) { '0 0 1 1 *' }
 
       let(:expected_next_run_at) do
-        Gitlab::Ci::CronParser.new(new_cron, pipeline_schedule.cron_timezone).
-          next_time_from(Time.now)
+        Gitlab::Ci::CronParser.new(new_cron, pipeline_schedule.cron_timezone)
+          .next_time_from(Time.now)
       end
 
       it 'updates next_run_at automatically' do
@@ -72,8 +72,8 @@ describe Ci::PipelineSchedule, models: true do
       let(:future_time) { 10.days.from_now }
 
       let(:expected_next_run_at) do
-        Gitlab::Ci::CronParser.new(pipeline_schedule.cron, pipeline_schedule.cron_timezone).
-          next_time_from(future_time)
+        Gitlab::Ci::CronParser.new(pipeline_schedule.cron, pipeline_schedule.cron_timezone)
+          .next_time_from(future_time)
       end
 
       it 'points to proper next_run_at' do
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index e86cbe8498a5b72d95f3a15dc326fbd8f8e315e7..ba0696fa2108c53350e6b374ca04b0b97cf0e01f 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -608,8 +608,8 @@ describe Ci::Pipeline, models: true do
 
       it 'returns the latest pipeline for the same ref and different sha' do
         expect(pipelines.map(&:sha)).to contain_exactly('A', 'B', 'C')
-        expect(pipelines.map(&:status)).
-          to contain_exactly('success', 'failed', 'skipped')
+        expect(pipelines.map(&:status))
+          .to contain_exactly('success', 'failed', 'skipped')
       end
     end
 
@@ -618,8 +618,8 @@ describe Ci::Pipeline, models: true do
 
       it 'returns the latest pipeline for ref and different sha' do
         expect(pipelines.map(&:sha)).to contain_exactly('A', 'B')
-        expect(pipelines.map(&:status)).
-          to contain_exactly('success', 'failed')
+        expect(pipelines.map(&:status))
+          .to contain_exactly('success', 'failed')
       end
     end
   end
@@ -654,8 +654,8 @@ describe Ci::Pipeline, models: true do
     end
 
     it 'returns the latest successful pipeline' do
-      expect(described_class.latest_successful_for('ref')).
-        to eq(latest_successful_pipeline)
+      expect(described_class.latest_successful_for('ref'))
+        .to eq(latest_successful_pipeline)
     end
   end
 
@@ -672,6 +672,12 @@ describe Ci::Pipeline, models: true do
     end
   end
 
+  describe '.internal_sources' do
+    subject { described_class.internal_sources }
+
+    it { is_expected.to be_an(Array) }
+  end
+
   describe '#status' do
     let(:build) do
       create(:ci_build, :created, pipeline: pipeline, name: 'test')
@@ -742,6 +748,39 @@ describe Ci::Pipeline, models: true do
     end
   end
 
+  describe '#ci_yaml_file_path' do
+    subject { pipeline.ci_yaml_file_path }
+
+    it 'returns the path from project' do
+      allow(pipeline.project).to receive(:ci_config_path) { 'custom/path' }
+
+      is_expected.to eq('custom/path')
+    end
+
+    it 'returns default when custom path is nil' do
+      allow(pipeline.project).to receive(:ci_config_path) { nil }
+
+      is_expected.to eq('.gitlab-ci.yml')
+    end
+
+    it 'returns default when custom path is empty' do
+      allow(pipeline.project).to receive(:ci_config_path) { '' }
+
+      is_expected.to eq('.gitlab-ci.yml')
+    end
+  end
+
+  describe '#ci_yaml_file' do
+    it 'reports error if the file is not found' do
+      allow(pipeline.project).to receive(:ci_config_path) { 'custom' }
+
+      pipeline.ci_yaml_file
+
+      expect(pipeline.yaml_errors)
+        .to eq('Failed to load CI/CD config file at custom')
+    end
+  end
+
   describe '#detailed_status' do
     subject { pipeline.detailed_status(user) }
 
@@ -1201,8 +1240,8 @@ describe Ci::Pipeline, models: true do
     before do
       project.team << [pipeline.user, Gitlab::Access::DEVELOPER]
 
-      pipeline.user.global_notification_setting.
-        update(level: 'custom', failed_pipeline: true, success_pipeline: true)
+      pipeline.user.global_notification_setting
+        .update(level: 'custom', failed_pipeline: true, success_pipeline: true)
 
       reset_delivered_emails!
 
diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb
index 077b10227d7357960c8c1775a8ec65ea8de09b10..50f7c029af8902e54c847204f04d6c39c510c857 100644
--- a/spec/models/ci/variable_spec.rb
+++ b/spec/models/ci/variable_spec.rb
@@ -5,12 +5,14 @@ describe Ci::Variable, models: true do
 
   let(:secret_value) { 'secret' }
 
-  it { is_expected.to validate_presence_of(:key) }
-  it { is_expected.to validate_uniqueness_of(:key).scoped_to(:project_id) }
-  it { is_expected.to validate_length_of(:key).is_at_most(255) }
-  it { is_expected.to allow_value('foo').for(:key) }
-  it { is_expected.not_to allow_value('foo bar').for(:key) }
-  it { is_expected.not_to allow_value('foo/bar').for(:key) }
+  describe 'validations' do
+    it { is_expected.to include_module(HasVariable) }
+    it { is_expected.to validate_uniqueness_of(:key).scoped_to(:project_id, :environment_scope) }
+    it { is_expected.to validate_length_of(:key).is_at_most(255) }
+    it { is_expected.to allow_value('foo').for(:key) }
+    it { is_expected.not_to allow_value('foo bar').for(:key) }
+    it { is_expected.not_to allow_value('foo/bar').for(:key) }
+  end
 
   describe '.unprotected' do
     subject { described_class.unprotected }
@@ -33,36 +35,4 @@ describe Ci::Variable, models: true do
       end
     end
   end
-
-  describe '#value' do
-    before do
-      subject.value = secret_value
-    end
-
-    it 'stores the encrypted value' do
-      expect(subject.encrypted_value).not_to be_nil
-    end
-
-    it 'stores an iv for value' do
-      expect(subject.encrypted_value_iv).not_to be_nil
-    end
-
-    it 'stores a salt for value' do
-      expect(subject.encrypted_value_salt).not_to be_nil
-    end
-
-    it 'fails to decrypt if iv is incorrect' do
-      subject.encrypted_value_iv = SecureRandom.hex
-      subject.instance_variable_set(:@value, nil)
-      expect { subject.value }.
-        to raise_error(OpenSSL::Cipher::CipherError, 'bad decrypt')
-    end
-  end
-
-  describe '#to_runner_variable' do
-    it 'returns a hash for the runner' do
-      expect(subject.to_runner_variable)
-        .to eq(key: subject.key, value: subject.value, public: false)
-    end
-  end
 end
diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb
index e4bddf670964e5101629134fe607cf36b4cecc49..ba9c3f66d213a196dcfb8924d5b8a69d2aca60d9 100644
--- a/spec/models/commit_range_spec.rb
+++ b/spec/models/commit_range_spec.rb
@@ -147,9 +147,9 @@ describe CommitRange, models: true do
              note: commit1.revert_description(user),
              project: issue.project)
 
-      expect_any_instance_of(Commit).to receive(:reverts_commit?).
-        with(commit1, user).
-        and_return(true)
+      expect_any_instance_of(Commit).to receive(:reverts_commit?)
+        .with(commit1, user)
+        .and_return(true)
 
       expect(commit1.has_been_reverted?(user, issue)).to eq(true)
     end
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index 9262ce08987d30aa1ad620e20e9512fb7845e9bc..1e074c7ad260a1b38fe055a86952a91eee29e9b3 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -284,6 +284,41 @@ describe CommitStatus, :models do
     end
   end
 
+  describe '.status' do
+    context 'when there are multiple statuses present' do
+      before do
+        create_status(status: 'running')
+        create_status(status: 'success')
+        create_status(allow_failure: true, status: 'failed')
+      end
+
+      it 'returns a correct compound status' do
+        expect(described_class.all.status).to eq 'running'
+      end
+    end
+
+    context 'when there are only allowed to fail commit statuses present' do
+      before do
+        create_status(allow_failure: true, status: 'failed')
+      end
+
+      it 'returns status that indicates success' do
+        expect(described_class.all.status).to eq 'success'
+      end
+    end
+
+    context 'when using a scope to select latest statuses' do
+      before do
+        create_status(name: 'test', retried: true, status: 'failed')
+        create_status(allow_failure: true, name: 'test', status: 'failed')
+      end
+
+      it 'returns status according to the scope' do
+        expect(described_class.latest.status).to eq 'success'
+      end
+    end
+  end
+
   describe '#before_sha' do
     subject { commit_status.before_sha }
 
diff --git a/spec/models/concerns/case_sensitivity_spec.rb b/spec/models/concerns/case_sensitivity_spec.rb
index 92fdc5cd65da4008f691869bdbd8ea87649dc077..a6fccb668e3a8762a378a3a9eeb2070d82b1ab71 100644
--- a/spec/models/concerns/case_sensitivity_spec.rb
+++ b/spec/models/concerns/case_sensitivity_spec.rb
@@ -15,13 +15,13 @@ describe CaseSensitivity, models: true do
         it 'returns the criteria for a column and a value' do
           criteria = double(:criteria)
 
-          expect(connection).to receive(:quote_table_name).
-            with(:foo).
-            and_return('"foo"')
+          expect(connection).to receive(:quote_table_name)
+            .with(:foo)
+            .and_return('"foo"')
 
-          expect(model).to receive(:where).
-            with(%q{LOWER("foo") = LOWER(:value)}, value: 'bar').
-            and_return(criteria)
+          expect(model).to receive(:where)
+            .with(%q{LOWER("foo") = LOWER(:value)}, value: 'bar')
+            .and_return(criteria)
 
           expect(model.iwhere(foo: 'bar')).to eq(criteria)
         end
@@ -29,13 +29,13 @@ describe CaseSensitivity, models: true do
         it 'returns the criteria for a column with a table, and a value' do
           criteria = double(:criteria)
 
-          expect(connection).to receive(:quote_table_name).
-            with(:'foo.bar').
-            and_return('"foo"."bar"')
+          expect(connection).to receive(:quote_table_name)
+            .with(:'foo.bar')
+            .and_return('"foo"."bar"')
 
-          expect(model).to receive(:where).
-            with(%q{LOWER("foo"."bar") = LOWER(:value)}, value: 'bar').
-            and_return(criteria)
+          expect(model).to receive(:where)
+            .with(%q{LOWER("foo"."bar") = LOWER(:value)}, value: 'bar')
+            .and_return(criteria)
 
           expect(model.iwhere('foo.bar'.to_sym => 'bar')).to eq(criteria)
         end
@@ -46,21 +46,21 @@ describe CaseSensitivity, models: true do
           initial = double(:criteria)
           final   = double(:criteria)
 
-          expect(connection).to receive(:quote_table_name).
-            with(:foo).
-            and_return('"foo"')
+          expect(connection).to receive(:quote_table_name)
+            .with(:foo)
+            .and_return('"foo"')
 
-          expect(connection).to receive(:quote_table_name).
-            with(:bar).
-            and_return('"bar"')
+          expect(connection).to receive(:quote_table_name)
+            .with(:bar)
+            .and_return('"bar"')
 
-          expect(model).to receive(:where).
-            with(%q{LOWER("foo") = LOWER(:value)}, value: 'bar').
-            and_return(initial)
+          expect(model).to receive(:where)
+            .with(%q{LOWER("foo") = LOWER(:value)}, value: 'bar')
+            .and_return(initial)
 
-          expect(initial).to receive(:where).
-            with(%q{LOWER("bar") = LOWER(:value)}, value: 'baz').
-            and_return(final)
+          expect(initial).to receive(:where)
+            .with(%q{LOWER("bar") = LOWER(:value)}, value: 'baz')
+            .and_return(final)
 
           got = model.iwhere(foo: 'bar', bar: 'baz')
 
@@ -71,21 +71,21 @@ describe CaseSensitivity, models: true do
           initial = double(:criteria)
           final   = double(:criteria)
 
-          expect(connection).to receive(:quote_table_name).
-            with(:'foo.bar').
-            and_return('"foo"."bar"')
+          expect(connection).to receive(:quote_table_name)
+            .with(:'foo.bar')
+            .and_return('"foo"."bar"')
 
-          expect(connection).to receive(:quote_table_name).
-            with(:'foo.baz').
-            and_return('"foo"."baz"')
+          expect(connection).to receive(:quote_table_name)
+            .with(:'foo.baz')
+            .and_return('"foo"."baz"')
 
-          expect(model).to receive(:where).
-            with(%q{LOWER("foo"."bar") = LOWER(:value)}, value: 'bar').
-            and_return(initial)
+          expect(model).to receive(:where)
+            .with(%q{LOWER("foo"."bar") = LOWER(:value)}, value: 'bar')
+            .and_return(initial)
 
-          expect(initial).to receive(:where).
-            with(%q{LOWER("foo"."baz") = LOWER(:value)}, value: 'baz').
-            and_return(final)
+          expect(initial).to receive(:where)
+            .with(%q{LOWER("foo"."baz") = LOWER(:value)}, value: 'baz')
+            .and_return(final)
 
           got = model.iwhere('foo.bar'.to_sym => 'bar',
                              'foo.baz'.to_sym => 'baz')
@@ -105,13 +105,13 @@ describe CaseSensitivity, models: true do
         it 'returns the criteria for a column and a value' do
           criteria = double(:criteria)
 
-          expect(connection).to receive(:quote_table_name).
-            with(:foo).
-            and_return('`foo`')
+          expect(connection).to receive(:quote_table_name)
+            .with(:foo)
+            .and_return('`foo`')
 
-          expect(model).to receive(:where).
-            with(%q{`foo` = :value}, value: 'bar').
-            and_return(criteria)
+          expect(model).to receive(:where)
+            .with(%q{`foo` = :value}, value: 'bar')
+            .and_return(criteria)
 
           expect(model.iwhere(foo: 'bar')).to eq(criteria)
         end
@@ -119,16 +119,16 @@ describe CaseSensitivity, models: true do
         it 'returns the criteria for a column with a table, and a value' do
           criteria = double(:criteria)
 
-          expect(connection).to receive(:quote_table_name).
-            with(:'foo.bar').
-            and_return('`foo`.`bar`')
+          expect(connection).to receive(:quote_table_name)
+            .with(:'foo.bar')
+            .and_return('`foo`.`bar`')
 
-          expect(model).to receive(:where).
-            with(%q{`foo`.`bar` = :value}, value: 'bar').
-            and_return(criteria)
+          expect(model).to receive(:where)
+            .with(%q{`foo`.`bar` = :value}, value: 'bar')
+            .and_return(criteria)
 
-          expect(model.iwhere('foo.bar'.to_sym => 'bar')).
-            to eq(criteria)
+          expect(model.iwhere('foo.bar'.to_sym => 'bar'))
+            .to eq(criteria)
         end
       end
 
@@ -137,21 +137,21 @@ describe CaseSensitivity, models: true do
           initial = double(:criteria)
           final   = double(:criteria)
 
-          expect(connection).to receive(:quote_table_name).
-            with(:foo).
-            and_return('`foo`')
+          expect(connection).to receive(:quote_table_name)
+            .with(:foo)
+            .and_return('`foo`')
 
-          expect(connection).to receive(:quote_table_name).
-            with(:bar).
-            and_return('`bar`')
+          expect(connection).to receive(:quote_table_name)
+            .with(:bar)
+            .and_return('`bar`')
 
-          expect(model).to receive(:where).
-            with(%q{`foo` = :value}, value: 'bar').
-            and_return(initial)
+          expect(model).to receive(:where)
+            .with(%q{`foo` = :value}, value: 'bar')
+            .and_return(initial)
 
-          expect(initial).to receive(:where).
-            with(%q{`bar` = :value}, value: 'baz').
-            and_return(final)
+          expect(initial).to receive(:where)
+            .with(%q{`bar` = :value}, value: 'baz')
+            .and_return(final)
 
           got = model.iwhere(foo: 'bar', bar: 'baz')
 
@@ -162,21 +162,21 @@ describe CaseSensitivity, models: true do
           initial = double(:criteria)
           final   = double(:criteria)
 
-          expect(connection).to receive(:quote_table_name).
-            with(:'foo.bar').
-            and_return('`foo`.`bar`')
+          expect(connection).to receive(:quote_table_name)
+            .with(:'foo.bar')
+            .and_return('`foo`.`bar`')
 
-          expect(connection).to receive(:quote_table_name).
-            with(:'foo.baz').
-            and_return('`foo`.`baz`')
+          expect(connection).to receive(:quote_table_name)
+            .with(:'foo.baz')
+            .and_return('`foo`.`baz`')
 
-          expect(model).to receive(:where).
-            with(%q{`foo`.`bar` = :value}, value: 'bar').
-            and_return(initial)
+          expect(model).to receive(:where)
+            .with(%q{`foo`.`bar` = :value}, value: 'bar')
+            .and_return(initial)
 
-          expect(initial).to receive(:where).
-            with(%q{`foo`.`baz` = :value}, value: 'baz').
-            and_return(final)
+          expect(initial).to receive(:where)
+            .with(%q{`foo`.`baz` = :value}, value: 'baz')
+            .and_return(final)
 
           got = model.iwhere('foo.bar'.to_sym => 'bar',
                              'foo.baz'.to_sym => 'baz')
diff --git a/spec/models/concerns/feature_gate_spec.rb b/spec/models/concerns/feature_gate_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3f601243245588ff49427427f287524b7ef46028
--- /dev/null
+++ b/spec/models/concerns/feature_gate_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe FeatureGate do
+  describe 'User' do
+    describe '#flipper_id' do
+      context 'when user is not persisted' do
+        let(:user) { build(:user) }
+
+        it { expect(user.flipper_id).to be_nil }
+      end
+
+      context 'when user is persisted' do
+        let(:user) { create(:user) }
+
+        it { expect(user.flipper_id).to eq "User:#{user.id}" }
+      end
+    end
+  end
+end
diff --git a/spec/models/concerns/has_status_spec.rb b/spec/models/concerns/has_status_spec.rb
index 67dae7cf4c0d0beaed9c67ff534f34fe16d680b7..a38f2553eb178fb15b6145173ce5cb503d29f293 100644
--- a/spec/models/concerns/has_status_spec.rb
+++ b/spec/models/concerns/has_status_spec.rb
@@ -48,7 +48,7 @@ describe HasStatus do
           [create(type, status: :failed, allow_failure: true)]
         end
 
-        it { is_expected.to eq 'skipped' }
+        it { is_expected.to eq 'success' }
       end
 
       context 'success and canceled' do
@@ -168,8 +168,8 @@ describe HasStatus do
 
           describe ".#{status}" do
             it 'contains the job' do
-              expect(CommitStatus.public_send(status).all).
-                to contain_exactly(job)
+              expect(CommitStatus.public_send(status).all)
+                .to contain_exactly(job)
             end
           end
 
diff --git a/spec/models/concerns/has_variable_spec.rb b/spec/models/concerns/has_variable_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f4b24e6d1d9b548067a3b4caaf968a94bee3597c
--- /dev/null
+++ b/spec/models/concerns/has_variable_spec.rb
@@ -0,0 +1,43 @@
+require 'spec_helper'
+
+describe HasVariable do
+  subject { build(:ci_variable) }
+
+  it { is_expected.to validate_presence_of(:key) }
+  it { is_expected.to validate_length_of(:key).is_at_most(255) }
+  it { is_expected.to allow_value('foo').for(:key) }
+  it { is_expected.not_to allow_value('foo bar').for(:key) }
+  it { is_expected.not_to allow_value('foo/bar').for(:key) }
+
+  describe '#value' do
+    before do
+      subject.value = 'secret'
+    end
+
+    it 'stores the encrypted value' do
+      expect(subject.encrypted_value).not_to be_nil
+    end
+
+    it 'stores an iv for value' do
+      expect(subject.encrypted_value_iv).not_to be_nil
+    end
+
+    it 'stores a salt for value' do
+      expect(subject.encrypted_value_salt).not_to be_nil
+    end
+
+    it 'fails to decrypt if iv is incorrect' do
+      subject.encrypted_value_iv = SecureRandom.hex
+      subject.instance_variable_set(:@value, nil)
+      expect { subject.value }
+        .to raise_error(OpenSSL::Cipher::CipherError, 'bad decrypt')
+    end
+  end
+
+  describe '#to_runner_variable' do
+    it 'returns a hash for the runner' do
+      expect(subject.to_runner_variable)
+        .to eq(key: subject.key, value: subject.value, public: false)
+    end
+  end
+end
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 1a9bda64191841eadd70b2751c2ef64316f9c7cc..ac9303370abc0bd7dabaee30f5845a4895959ee8 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -69,8 +69,8 @@ describe Issuable do
     let!(:searchable_issue) { create(:issue, title: "Searchable issue") }
 
     it 'returns notes with a matching title' do
-      expect(issuable_class.search(searchable_issue.title)).
-        to eq([searchable_issue])
+      expect(issuable_class.search(searchable_issue.title))
+        .to eq([searchable_issue])
     end
 
     it 'returns notes with a partially matching title' do
@@ -78,8 +78,8 @@ describe Issuable do
     end
 
     it 'returns notes with a matching title regardless of the casing' do
-      expect(issuable_class.search(searchable_issue.title.upcase)).
-        to eq([searchable_issue])
+      expect(issuable_class.search(searchable_issue.title.upcase))
+        .to eq([searchable_issue])
     end
   end
 
@@ -89,8 +89,8 @@ describe Issuable do
     end
 
     it 'returns notes with a matching title' do
-      expect(issuable_class.full_search(searchable_issue.title)).
-        to eq([searchable_issue])
+      expect(issuable_class.full_search(searchable_issue.title))
+        .to eq([searchable_issue])
     end
 
     it 'returns notes with a partially matching title' do
@@ -98,23 +98,23 @@ describe Issuable do
     end
 
     it 'returns notes with a matching title regardless of the casing' do
-      expect(issuable_class.full_search(searchable_issue.title.upcase)).
-        to eq([searchable_issue])
+      expect(issuable_class.full_search(searchable_issue.title.upcase))
+        .to eq([searchable_issue])
     end
 
     it 'returns notes with a matching description' do
-      expect(issuable_class.full_search(searchable_issue.description)).
-        to eq([searchable_issue])
+      expect(issuable_class.full_search(searchable_issue.description))
+        .to eq([searchable_issue])
     end
 
     it 'returns notes with a partially matching description' do
-      expect(issuable_class.full_search(searchable_issue.description)).
-        to eq([searchable_issue])
+      expect(issuable_class.full_search(searchable_issue.description))
+        .to eq([searchable_issue])
     end
 
     it 'returns notes with a matching description regardless of the casing' do
-      expect(issuable_class.full_search(searchable_issue.description.upcase)).
-        to eq([searchable_issue])
+      expect(issuable_class.full_search(searchable_issue.description.upcase))
+        .to eq([searchable_issue])
     end
   end
 
diff --git a/spec/models/concerns/milestoneish_spec.rb b/spec/models/concerns/milestoneish_spec.rb
index 675b730c5575ab80a4a8aa2c4a9d2fe8d9325a85..cefe7fb6feadf6698ca06a86d4a679a599038a10 100644
--- a/spec/models/concerns/milestoneish_spec.rb
+++ b/spec/models/concerns/milestoneish_spec.rb
@@ -19,12 +19,43 @@ describe Milestone, 'Milestoneish' do
   let!(:closed_security_issue_3) { create(:issue, :confidential, :closed, project: project, author: author, milestone: milestone) }
   let!(:closed_security_issue_4) { create(:issue, :confidential, :closed, project: project, assignees: [assignee], milestone: milestone) }
   let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, milestone: milestone) }
+  let(:label_1) { create(:label, title: 'label_1', project: project, priority: 1) }
+  let(:label_2) { create(:label, title: 'label_2', project: project, priority: 2) }
+  let(:label_3) { create(:label, title: 'label_3', project: project) }
 
   before do
     project.team << [member, :developer]
     project.team << [guest, :guest]
   end
 
+  describe '#sorted_issues' do
+    it 'sorts issues by label priority' do
+      issue.labels << label_1
+      security_issue_1.labels << label_2
+      closed_issue_1.labels << label_3
+
+      issues = milestone.sorted_issues(member)
+
+      expect(issues.first).to eq(issue)
+      expect(issues.second).to eq(security_issue_1)
+      expect(issues.third).not_to eq(closed_issue_1)
+    end
+  end
+
+  describe '#sorted_merge_requests' do
+    it 'sorts merge requests by label priority' do
+      merge_request_1 = create(:labeled_merge_request, labels: [label_2], source_project: project, source_branch: 'branch_1', milestone: milestone)
+      merge_request_2 = create(:labeled_merge_request, labels: [label_1], source_project: project, source_branch: 'branch_2', milestone: milestone)
+      merge_request_3 = create(:labeled_merge_request, labels: [label_3], source_project: project, source_branch: 'branch_3', milestone: milestone)
+
+      merge_requests = milestone.sorted_merge_requests
+
+      expect(merge_requests.first).to eq(merge_request_2)
+      expect(merge_requests.second).to eq(merge_request_1)
+      expect(merge_requests.third).to eq(merge_request_3)
+    end
+  end
+
   describe '#closed_items_count' do
     it 'does not count confidential issues for non project members' do
       expect(milestone.closed_items_count(non_member)).to eq 2
diff --git a/spec/models/concerns/resolvable_discussion_spec.rb b/spec/models/concerns/resolvable_discussion_spec.rb
index 18327fe262db2e4e6fe1d3a53a6e9d98ec3cc2ec..3934992c14333ab6601ad7f80fe87b6361c1e9d7 100644
--- a/spec/models/concerns/resolvable_discussion_spec.rb
+++ b/spec/models/concerns/resolvable_discussion_spec.rb
@@ -306,22 +306,22 @@ describe Discussion, ResolvableDiscussion, models: true do
         it "doesn't change resolved_at on the resolved note" do
           expect(first_note.resolved_at).not_to be_nil
 
-          expect { subject.resolve!(current_user) }.
-            not_to change { first_note.reload.resolved_at }
+          expect { subject.resolve!(current_user) }
+            .not_to change { first_note.reload.resolved_at }
         end
 
         it "doesn't change resolved_by on the resolved note" do
           expect(first_note.resolved_by).to eq(user)
 
-          expect { subject.resolve!(current_user) }.
-            not_to change { first_note.reload && first_note.resolved_by }
+          expect { subject.resolve!(current_user) }
+            .not_to change { first_note.reload && first_note.resolved_by }
         end
 
         it "doesn't change the resolved state on the resolved note" do
           expect(first_note.resolved?).to be true
 
-          expect { subject.resolve!(current_user) }.
-            not_to change { first_note.reload && first_note.resolved? }
+          expect { subject.resolve!(current_user) }
+            .not_to change { first_note.reload && first_note.resolved? }
         end
 
         it "sets resolved_at on the unresolved note" do
diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb
index 65f05121b403bf538f465a57c3e692f1f212e160..36aedd2f70142c156611f8c1b0c2297876f52bd2 100644
--- a/spec/models/concerns/routable_spec.rb
+++ b/spec/models/concerns/routable_spec.rb
@@ -132,6 +132,19 @@ describe Group, 'Routable' do
     end
   end
 
+  describe '#expires_full_path_cache' do
+    context 'with RequestStore active', :request_store do
+      it 'expires the full_path cache' do
+        expect(group.full_path).to eq('foo')
+
+        group.route.update(path: 'bar', name: 'bar')
+        group.expires_full_path_cache
+
+        expect(group.full_path).to eq('bar')
+      end
+    end
+  end
+
   describe '#full_name' do
     let(:group) { create(:group) }
     let(:nested_group) { create(:group, parent: group) }
diff --git a/spec/models/concerns/sha_attribute_spec.rb b/spec/models/concerns/sha_attribute_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9e37c2b20c4aa50d850728c3dd626f67a38c785c
--- /dev/null
+++ b/spec/models/concerns/sha_attribute_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe ShaAttribute do
+  let(:model) { Class.new { include ShaAttribute } }
+
+  before do
+    columns = [
+      double(:column, name: 'name', type: :text),
+      double(:column, name: 'sha1', type: :binary)
+    ]
+
+    allow(model).to receive(:columns).and_return(columns)
+  end
+
+  describe '#sha_attribute' do
+    it 'defines a SHA attribute for a binary column' do
+      expect(model).to receive(:attribute)
+        .with(:sha1, an_instance_of(Gitlab::Database::ShaAttribute))
+
+      model.sha_attribute(:sha1)
+    end
+
+    it 'raises ArgumentError when the column type is not :binary' do
+      expect { model.sha_attribute(:name) }.to raise_error(ArgumentError)
+    end
+  end
+end
diff --git a/spec/models/concerns/sortable_spec.rb b/spec/models/concerns/sortable_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d1e17c4f6845342c6102b75e9bca14566da4131e
--- /dev/null
+++ b/spec/models/concerns/sortable_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe Sortable do
+  let(:relation) { Issue.all }
+
+  describe '#where' do
+    it 'orders by id, descending' do
+      order_node = relation.where(iid: 1).order_values.first
+      expect(order_node).to be_a(Arel::Nodes::Descending)
+      expect(order_node.expr.name).to eq(:id)
+    end
+  end
+
+  describe '#find_by' do
+    it 'does not order' do
+      expect(relation).to receive(:unscope).with(:order).and_call_original
+
+      relation.find_by(iid: 1)
+    end
+  end
+end
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index aad215d5f416fbf1a8385697f5726490f92c258d..bb84d3fc13df2a4c5e0eed0a04c085b37484c55b 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -30,7 +30,7 @@ describe Deployment, models: true do
   end
 
   describe '#includes_commit?' do
-    let(:project)     { create(:project, :repository) }
+    let(:project) { create(:project, :repository) }
     let(:environment) { create(:environment, project: project) }
     let(:deployment) do
       create(:deployment, environment: environment, sha: project.commit.id)
@@ -90,6 +90,36 @@ describe Deployment, models: true do
     end
   end
 
+  describe '#additional_metrics' do
+    let(:project) { create(:project) }
+    let(:deployment) { create(:deployment, project: project) }
+
+    subject { deployment.additional_metrics }
+
+    context 'metrics are disabled' do
+      it { is_expected.to eq({}) }
+    end
+
+    context 'metrics are enabled' do
+      let(:simple_metrics) do
+        {
+          success: true,
+          metrics: {},
+          last_update: 42
+        }
+      end
+
+      let(:prometheus_service) { double('prometheus_service') }
+
+      before do
+        allow(project).to receive(:prometheus_service).and_return(prometheus_service)
+        allow(prometheus_service).to receive(:additional_deployment_metrics).and_return(simple_metrics)
+      end
+
+      it { is_expected.to eq(simple_metrics.merge({ deployment_time: deployment.created_at.to_i })) }
+    end
+  end
+
   describe '#stop_action' do
     let(:build) { create(:ci_build) }
 
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index f8123cb518ebe79d956dbc83fa6f394e8f0181da..0a2cd8c295714a6eeae1cd8d8e2ce354999a69b2 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -120,28 +120,17 @@ describe Environment, models: true do
     let(:head_commit)   { project.commit }
     let(:commit)        { project.commit.parent }
 
-    context 'Gitaly find_ref_name feature disabled' do
-      it 'returns deployment id for the environment' do
-        expect(environment.first_deployment_for(commit)).to eq deployment1
-      end
+    it 'returns deployment id for the environment' do
+      expect(environment.first_deployment_for(commit)).to eq deployment1
+    end
 
-      it 'return nil when no deployment is found' do
-        expect(environment.first_deployment_for(head_commit)).to eq nil
-      end
+    it 'return nil when no deployment is found' do
+      expect(environment.first_deployment_for(head_commit)).to eq nil
     end
 
-    # TODO: Uncomment when feature is reenabled
-    # context 'Gitaly find_ref_name feature enabled' do
-    #   before do
-    #     allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:find_ref_name).and_return(true)
-    #   end
-    #
-    #   it 'calls GitalyClient' do
-    #     expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:find_ref_name)
-    #
-    #     environment.first_deployment_for(commit)
-    #   end
-    # end
+    it 'returns a UTF-8 ref' do
+      expect(environment.first_deployment_for(commit).ref).to be_utf8
+    end
   end
 
   describe '#environment_type' do
@@ -432,6 +421,99 @@ describe Environment, models: true do
     end
   end
 
+  describe '#has_metrics?' do
+    subject { environment.has_metrics? }
+
+    context 'when the enviroment is available' do
+      context 'with a deployment service' do
+        let(:project) { create(:prometheus_project) }
+
+        context 'and a deployment' do
+          let!(:deployment) { create(:deployment, environment: environment) }
+          it { is_expected.to be_truthy }
+        end
+
+        context 'but no deployments' do
+          it { is_expected.to be_falsy }
+        end
+      end
+
+      context 'without a monitoring service' do
+        it { is_expected.to be_falsy }
+      end
+    end
+
+    context 'when the environment is unavailable' do
+      let(:project) { create(:prometheus_project) }
+
+      before do
+        environment.stop
+      end
+
+      it { is_expected.to be_falsy }
+    end
+  end
+
+  describe '#additional_metrics' do
+    let(:project) { create(:prometheus_project) }
+    subject { environment.additional_metrics }
+
+    context 'when the environment has additional metrics' do
+      before do
+        allow(environment).to receive(:has_additional_metrics?).and_return(true)
+      end
+
+      it 'returns the additional metrics from the deployment service' do
+        expect(project.prometheus_service).to receive(:additional_environment_metrics)
+                                                .with(environment)
+                                                .and_return(:fake_metrics)
+
+        is_expected.to eq(:fake_metrics)
+      end
+    end
+
+    context 'when the environment does not have metrics' do
+      before do
+        allow(environment).to receive(:has_additional_metrics?).and_return(false)
+      end
+
+      it { is_expected.to be_nil }
+    end
+  end
+
+  describe '#has_additional_metrics??' do
+    subject { environment.has_additional_metrics? }
+
+    context 'when the enviroment is available' do
+      context 'with a deployment service' do
+        let(:project) { create(:prometheus_project) }
+
+        context 'and a deployment' do
+          let!(:deployment) { create(:deployment, environment: environment) }
+          it { is_expected.to be_truthy }
+        end
+
+        context 'but no deployments' do
+          it { is_expected.to be_falsy }
+        end
+      end
+
+      context 'without a monitoring service' do
+        it { is_expected.to be_falsy }
+      end
+    end
+
+    context 'when the environment is unavailable' do
+      let(:project) { create(:prometheus_project) }
+
+      before do
+        environment.stop
+      end
+
+      it { is_expected.to be_falsy }
+    end
+  end
+
   describe '#slug' do
     it "is automatically generated" do
       expect(environment.slug).not_to be_nil
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index b8cb967c4cc16098d51b6beab2146548fb0180b4..10b9bf9f43a76f735e3952a671aa23ee2afa0664 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -266,8 +266,8 @@ describe Event, models: true do
       it 'does not update the project' do
         project.update(last_activity_at: Time.now)
 
-        expect(project).not_to receive(:update_column).
-          with(:last_activity_at, a_kind_of(Time))
+        expect(project).not_to receive(:update_column)
+          .with(:last_activity_at, a_kind_of(Time))
 
         create_push_event(project, project.owner)
       end
diff --git a/spec/models/forked_project_link_spec.rb b/spec/models/forked_project_link_spec.rb
index 6e8d43f988cd94ec9c921a021b3ef0a1b8fba0cd..5c13cf584f9a58c18506f9ecafe8c2b58fd11b6d 100644
--- a/spec/models/forked_project_link_spec.rb
+++ b/spec/models/forked_project_link_spec.rb
@@ -2,53 +2,75 @@ require 'spec_helper'
 
 describe ForkedProjectLink, "add link on fork" do
   let(:project_from) { create(:project, :repository) }
+  let(:project_to) { fork_project(project_from, user) }
   let(:user) { create(:user) }
   let(:namespace) { user.namespace }
 
   before do
-    create(:project_member, :reporter, user: user, project: project_from)
-    @project_to = fork_project(project_from, user)
+    project_from.add_reporter(user)
+  end
+
+  it 'project_from knows its forks' do
+    _ = project_to
+
+    expect(project_from.forks.count).to eq(1)
   end
 
   it "project_to knows it is forked" do
-    expect(@project_to.forked?).to be_truthy
+    expect(project_to.forked?).to be_truthy
   end
 
   it "project knows who it is forked from" do
-    expect(@project_to.forked_from_project).to eq(project_from)
+    expect(project_to.forked_from_project).to eq(project_from)
   end
-end
 
-describe '#forked?' do
-  let(:forked_project_link) { build(:forked_project_link) }
-  let(:project_from) { create(:project, :repository) }
-  let(:project_to) { create(:project, forked_project_link: forked_project_link) }
+  context 'project_to is pending_delete' do
+    before do
+      project_to.update!(pending_delete: true)
+    end
 
-  before :each do
-    forked_project_link.forked_from_project = project_from
-    forked_project_link.forked_to_project = project_to
-    forked_project_link.save!
+    it { expect(project_from.forks.count).to eq(0) }
   end
 
-  it "project_to knows it is forked" do
-    expect(project_to.forked?).to be_truthy
-  end
+  context 'project_from is pending_delete' do
+    before do
+      project_from.update!(pending_delete: true)
+    end
 
-  it "project_from is not forked" do
-    expect(project_from.forked?).to be_falsey
+    it { expect(project_to.forked_from_project).to be_nil }
   end
 
-  it "project_to.destroy destroys fork_link" do
-    expect(forked_project_link).to receive(:destroy)
-    project_to.destroy
+  describe '#forked?' do
+    let(:project_to) { create(:project, forked_project_link: forked_project_link) }
+    let(:forked_project_link) { build(:forked_project_link) }
+
+    before do
+      forked_project_link.forked_from_project = project_from
+      forked_project_link.forked_to_project = project_to
+      forked_project_link.save!
+    end
+
+    it "project_to knows it is forked" do
+      expect(project_to.forked?).to be_truthy
+    end
+
+    it "project_from is not forked" do
+      expect(project_from.forked?).to be_falsey
+    end
+
+    it "project_to.destroy destroys fork_link" do
+      expect(forked_project_link).to receive(:destroy)
+
+      project_to.destroy
+    end
   end
-end
 
-def fork_project(from_project, user)
-  shell = double('gitlab_shell', fork_repository: true)
+  def fork_project(from_project, user)
+    service = Projects::ForkService.new(from_project, user)
+    shell = double('gitlab_shell', fork_repository: true)
 
-  service = Projects::ForkService.new(from_project, user)
-  allow(service).to receive(:gitlab_shell).and_return(shell)
+    allow(service).to receive(:gitlab_shell).and_return(shell)
 
-  service.execute
+    service.execute
+  end
 end
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 449b7c2f7d7f2dbd63a703151621aa2b58b16c3c..4de1683b21c4538a9362a773042ee46524b7f43f 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -374,8 +374,8 @@ describe Group, models: true do
       group.add_user(master, GroupMember::MASTER)
       group.add_user(developer, GroupMember::DEVELOPER)
 
-      expect(group.user_ids_for_project_authorizations).
-        to include(master.id, developer.id)
+      expect(group.user_ids_for_project_authorizations)
+        .to include(master.id, developer.id)
     end
   end
 
diff --git a/spec/models/issue_collection_spec.rb b/spec/models/issue_collection_spec.rb
index 93c2c538e1056053c74009a12bce5fda0246d0e4..04d23d4c4fdb73ad13ddc5ff534b1dfb0dc3b8a6 100644
--- a/spec/models/issue_collection_spec.rb
+++ b/spec/models/issue_collection_spec.rb
@@ -50,8 +50,8 @@ describe IssueCollection do
 
     context 'using a user that is the owner of a project' do
       it 'returns the issues of the project' do
-        expect(collection.updatable_by_user(project.namespace.owner)).
-          to eq([issue1, issue2])
+        expect(collection.updatable_by_user(project.namespace.owner))
+          .to eq([issue1, issue2])
       end
     end
   end
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index 12e7d646382df068be35e1cb098f475d65d09dd9..bf97c6ececd8d6c1f7eb237bfef379a128139b7d 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -33,8 +33,8 @@ describe Issue, models: true do
     let!(:issue4) { create(:issue, project: project, relative_position: 200) }
 
     it 'returns ordered list' do
-      expect(project.issues.order_by_position_and_priority).
-        to match [issue3, issue4, issue1, issue2]
+      expect(project.issues.order_by_position_and_priority)
+        .to match [issue3, issue4, issue1, issue2]
     end
   end
 
@@ -43,16 +43,16 @@ describe Issue, models: true do
       allow(subject).to receive(:author).and_return(double(name: 'Robert'))
       allow(subject).to receive(:assignees).and_return([])
 
-      expect(subject.card_attributes).
-        to eq({ 'Author' => 'Robert', 'Assignee' => '' })
+      expect(subject.card_attributes)
+        .to eq({ 'Author' => 'Robert', 'Assignee' => '' })
     end
 
     it 'includes the assignee name' do
       allow(subject).to receive(:author).and_return(double(name: 'Robert'))
       allow(subject).to receive(:assignees).and_return([double(name: 'Douwe')])
 
-      expect(subject.card_attributes).
-        to eq({ 'Author' => 'Robert', 'Assignee' => 'Douwe' })
+      expect(subject.card_attributes)
+        .to eq({ 'Author' => 'Robert', 'Assignee' => 'Douwe' })
     end
   end
 
@@ -299,8 +299,8 @@ describe Issue, models: true do
     let(:user) { build(:admin) }
 
     before do
-      allow(subject.project.repository).to receive(:branch_names).
-                                            and_return(["mpempe", "#{subject.iid}mepmep", subject.to_branch_name, "#{subject.iid}-branch"])
+      allow(subject.project.repository).to receive(:branch_names)
+                                            .and_return(["mpempe", "#{subject.iid}mepmep", subject.to_branch_name, "#{subject.iid}-branch"])
 
       # Without this stub, the `create(:merge_request)` above fails because it can't find
       # the source branch. This seems like a reasonable compromise, in comparison with
@@ -322,8 +322,8 @@ describe Issue, models: true do
     end
 
     it 'excludes stable branches from the related branches' do
-      allow(subject.project.repository).to receive(:branch_names).
-        and_return(["#{subject.iid}-0-stable"])
+      allow(subject.project.repository).to receive(:branch_names)
+        .and_return(["#{subject.iid}-0-stable"])
 
       expect(subject.related_branches(user)).to eq []
     end
diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb
index f1e2a2cc518da7141695e7b20447c32778ed0913..f27920f9feb576ab74156feaa786174a54ee7cad 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -34,8 +34,8 @@ describe Key, models: true do
 
       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')
+          allow_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain)
+            .and_return('000000')
         end
 
         it 'enqueues a UseKeyWorker job' do
@@ -46,8 +46,8 @@ describe Key, models: true do
 
       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)
+          allow_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain)
+            .and_return(false)
         end
 
         it 'does not enqueue a UseKeyWorker job' do
diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb
index 84867e3d96b1fcc7a6f928d8efb8cdfa5c7e7548..31190fe5685e1970f6736e1c0cc7a9b9085f1164 100644
--- a/spec/models/label_spec.rb
+++ b/spec/models/label_spec.rb
@@ -59,8 +59,8 @@ describe Label, models: true do
 
   describe '#text_color' do
     it 'uses default color if color is missing' do
-      expect(LabelsHelper).to receive(:text_color_for_bg).with(Label::DEFAULT_COLOR).
-        and_return(spy)
+      expect(LabelsHelper).to receive(:text_color_for_bg).with(Label::DEFAULT_COLOR)
+        .and_return(spy)
 
       label = described_class.new(color: nil)
 
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index ccc3deac199b0b17601a96ea8d861674cc37f3a6..494a88368ba25931969d5f9ad4f91b9a37774236 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -83,8 +83,8 @@ describe Member, models: true do
       @accepted_invite_member = create(:project_member, :developer,
                                       project: project,
                                       invite_token: '1234',
-                                      invite_email: 'toto2@example.com').
-                                      tap { |u| u.accept_invite!(accepted_invite_user) }
+                                      invite_email: 'toto2@example.com')
+                                      .tap { |u| u.accept_invite!(accepted_invite_user) }
 
       requested_user = create(:user).tap { |u| project.request_access(u) }
       @requested_member = project.requesters.find_by(user_id: requested_user.id)
@@ -265,8 +265,8 @@ describe Member, models: true do
               expect(source.users).not_to include(user)
               expect(source.requesters.exists?(user_id: user)).to be_truthy
 
-              expect { described_class.add_user(source, user, :master) }.
-                to raise_error(Gitlab::Access::AccessDeniedError)
+              expect { described_class.add_user(source, user, :master) }
+                .to raise_error(Gitlab::Access::AccessDeniedError)
 
               expect(source.users.reload).not_to include(user)
               expect(source.requesters.reload.exists?(user_id: user)).to be_truthy
diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb
index 17765b25856071fa199ac720af30e4ec340c2bff..37014268a70acf02f42350290c1f16ae59bd1565 100644
--- a/spec/models/members/group_member_spec.rb
+++ b/spec/models/members/group_member_spec.rb
@@ -33,8 +33,8 @@ describe GroupMember, models: true do
       it "sends email to user" do
         membership = build(:group_member)
 
-        allow(membership).to receive(:notification_service).
-          and_return(double('NotificationService').as_null_object)
+        allow(membership).to receive(:notification_service)
+          .and_return(double('NotificationService').as_null_object)
         expect(membership).to receive(:notification_service)
 
         membership.save
@@ -44,8 +44,8 @@ describe GroupMember, models: true do
     describe "#after_update" do
       before do
         @group_member = create :group_member
-        allow(@group_member).to receive(:notification_service).
-          and_return(double('NotificationService').as_null_object)
+        allow(@group_member).to receive(:notification_service)
+          .and_return(double('NotificationService').as_null_object)
       end
 
       it "sends email to user" do
diff --git a/spec/models/merge_request_diff_file_spec.rb b/spec/models/merge_request_diff_file_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7276f5b5061d472ff4620d4ba8069dcbedec1293
--- /dev/null
+++ b/spec/models/merge_request_diff_file_spec.rb
@@ -0,0 +1,11 @@
+require 'rails_helper'
+
+describe MergeRequestDiffFile, type: :model do
+  describe '#utf8_diff' do
+    it 'does not raise error when a hash value is in binary' do
+      subject.diff = "\x05\x00\x68\x65\x6c\x6c\x6f"
+
+      expect { subject.utf8_diff }.not_to raise_error
+    end
+  end
+end
diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb
index 25f7062860b0dc238a9cf62c5767b480091934f7..4ad4abaa57219b14e35433bd39d07281920fa4ac 100644
--- a/spec/models/merge_request_diff_spec.rb
+++ b/spec/models/merge_request_diff_spec.rb
@@ -37,7 +37,7 @@ describe MergeRequestDiff, models: true do
 
     context 'when the raw diffs are empty' do
       before do
-        mr_diff.update_attributes(st_diffs: '')
+        MergeRequestDiffFile.delete_all(merge_request_diff_id: mr_diff.id)
       end
 
       it 'returns an empty DiffCollection' do
@@ -48,6 +48,7 @@ describe MergeRequestDiff, models: true do
 
     context 'when the raw diffs have invalid content' do
       before do
+        MergeRequestDiffFile.delete_all(merge_request_diff_id: mr_diff.id)
         mr_diff.update_attributes(st_diffs: ["--broken-diff"])
       end
 
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index cd2f11dec96204ac7f559cb1e7e4597cedddd221..587d4b83cb4694b5e7da4dc9061fee45f63709a7 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -92,16 +92,32 @@ describe MergeRequest, models: true do
       allow(subject).to receive(:author).and_return(double(name: 'Robert'))
       allow(subject).to receive(:assignee).and_return(nil)
 
-      expect(subject.card_attributes).
-        to eq({ 'Author' => 'Robert', 'Assignee' => nil })
+      expect(subject.card_attributes)
+        .to eq({ 'Author' => 'Robert', 'Assignee' => nil })
     end
 
     it 'includes the assignee name' do
       allow(subject).to receive(:author).and_return(double(name: 'Robert'))
       allow(subject).to receive(:assignee).and_return(double(name: 'Douwe'))
 
-      expect(subject.card_attributes).
-        to eq({ 'Author' => 'Robert', 'Assignee' => 'Douwe' })
+      expect(subject.card_attributes)
+        .to eq({ 'Author' => 'Robert', 'Assignee' => 'Douwe' })
+    end
+  end
+
+  describe '#assignee_ids' do
+    it 'returns an array of the assigned user id' do
+      subject.assignee_id = 123
+
+      expect(subject.assignee_ids).to eq([123])
+    end
+  end
+
+  describe '#assignee_ids=' do
+    it 'sets assignee_id to the last id in the array' do
+      subject.assignee_ids = [123, 456]
+
+      expect(subject.assignee_id).to eq(456)
     end
   end
 
@@ -361,8 +377,8 @@ describe MergeRequest, models: true do
     end
 
     it 'accesses the set of issues that will be closed on acceptance' do
-      allow(subject.project).to receive(:default_branch).
-        and_return(subject.target_branch)
+      allow(subject.project).to receive(:default_branch)
+        .and_return(subject.target_branch)
 
       closed = subject.closes_issues
 
@@ -388,8 +404,8 @@ describe MergeRequest, models: true do
       subject.description = "Is related to #{mentioned_issue.to_reference} and #{closing_issue.to_reference}"
 
       allow(subject).to receive(:commits).and_return([commit])
-      allow(subject.project).to receive(:default_branch).
-        and_return(subject.target_branch)
+      allow(subject.project).to receive(:default_branch)
+        .and_return(subject.target_branch)
 
       expect(subject.issues_mentioned_but_not_closing(subject.author)).to match_array([mentioned_issue])
     end
@@ -537,8 +553,8 @@ describe MergeRequest, models: true do
       subject.project.team << [subject.author, :developer]
       subject.description = "This issue Closes #{issue.to_reference}"
 
-      allow(subject.project).to receive(:default_branch).
-        and_return(subject.target_branch)
+      allow(subject.project).to receive(:default_branch)
+        .and_return(subject.target_branch)
 
       expect(subject.merge_commit_message)
         .to match("Closes #{issue.to_reference}")
@@ -663,18 +679,18 @@ describe MergeRequest, models: true do
       end
 
       it 'caches the output' do
-        expect(subject).to receive(:compute_diverged_commits_count).
-          once.
-          and_return(2)
+        expect(subject).to receive(:compute_diverged_commits_count)
+          .once
+          .and_return(2)
 
         subject.diverged_commits_count
         subject.diverged_commits_count
       end
 
       it 'invalidates the cache when the source sha changes' do
-        expect(subject).to receive(:compute_diverged_commits_count).
-          twice.
-          and_return(2)
+        expect(subject).to receive(:compute_diverged_commits_count)
+          .twice
+          .and_return(2)
 
         subject.diverged_commits_count
         allow(subject).to receive(:source_branch_sha).and_return('123abc')
@@ -682,9 +698,9 @@ describe MergeRequest, models: true do
       end
 
       it 'invalidates the cache when the target sha changes' do
-        expect(subject).to receive(:compute_diverged_commits_count).
-          twice.
-          and_return(2)
+        expect(subject).to receive(:compute_diverged_commits_count)
+          .twice
+          .and_return(2)
 
         subject.diverged_commits_count
         allow(subject).to receive(:target_branch_sha).and_return('123abc')
@@ -706,8 +722,8 @@ describe MergeRequest, models: true do
 
   describe '#commits_sha' do
     before do
-      allow(subject.merge_request_diff).to receive(:commits_sha).
-        and_return(['sha1'])
+      allow(subject.merge_request_diff).to receive(:commits_sha)
+        .and_return(['sha1'])
     end
 
     it 'delegates to merge request diff' do
@@ -1397,7 +1413,7 @@ describe MergeRequest, models: true do
     end
   end
 
-  describe '#mergeable_with_slash_command?' do
+  describe '#mergeable_with_quick_action?' do
     def create_pipeline(status)
       pipeline = create(:ci_pipeline_with_one_job,
         project: project,
@@ -1421,21 +1437,21 @@ describe MergeRequest, models: true do
 
     context 'when autocomplete_precheck is set to true' do
       it 'is mergeable by developer' do
-        expect(merge_request.mergeable_with_slash_command?(developer, autocomplete_precheck: true)).to be_truthy
+        expect(merge_request.mergeable_with_quick_action?(developer, autocomplete_precheck: true)).to be_truthy
       end
 
       it 'is not mergeable by normal user' do
-        expect(merge_request.mergeable_with_slash_command?(user, autocomplete_precheck: true)).to be_falsey
+        expect(merge_request.mergeable_with_quick_action?(user, autocomplete_precheck: true)).to be_falsey
       end
     end
 
     context 'when autocomplete_precheck is set to false' do
       it 'is mergeable by developer' do
-        expect(merge_request.mergeable_with_slash_command?(developer, last_diff_sha: mr_sha)).to be_truthy
+        expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_truthy
       end
 
       it 'is not mergeable by normal user' do
-        expect(merge_request.mergeable_with_slash_command?(user, last_diff_sha: mr_sha)).to be_falsey
+        expect(merge_request.mergeable_with_quick_action?(user, last_diff_sha: mr_sha)).to be_falsey
       end
 
       context 'closed MR'  do
@@ -1444,7 +1460,7 @@ describe MergeRequest, models: true do
         end
 
         it 'is not mergeable' do
-          expect(merge_request.mergeable_with_slash_command?(developer, last_diff_sha: mr_sha)).to be_falsey
+          expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_falsey
         end
       end
 
@@ -1454,19 +1470,19 @@ describe MergeRequest, models: true do
         end
 
         it 'is not mergeable' do
-          expect(merge_request.mergeable_with_slash_command?(developer, last_diff_sha: mr_sha)).to be_falsey
+          expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_falsey
         end
       end
 
       context 'sha differs from the MR diff_head_sha'  do
         it 'is not mergeable' do
-          expect(merge_request.mergeable_with_slash_command?(developer, last_diff_sha: 'some other sha')).to be_falsey
+          expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: 'some other sha')).to be_falsey
         end
       end
 
       context 'sha is not provided'  do
         it 'is not mergeable' do
-          expect(merge_request.mergeable_with_slash_command?(developer)).to be_falsey
+          expect(merge_request.mergeable_with_quick_action?(developer)).to be_falsey
         end
       end
 
@@ -1476,7 +1492,7 @@ describe MergeRequest, models: true do
         end
 
         it 'is mergeable' do
-          expect(merge_request.mergeable_with_slash_command?(developer, last_diff_sha: mr_sha)).to be_truthy
+          expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_truthy
         end
       end
 
@@ -1486,7 +1502,7 @@ describe MergeRequest, models: true do
         end
 
         it 'is not mergeable' do
-          expect(merge_request.mergeable_with_slash_command?(developer, last_diff_sha: mr_sha)).to be_falsey
+          expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_falsey
         end
       end
 
@@ -1496,7 +1512,7 @@ describe MergeRequest, models: true do
         end
 
         it 'is mergeable' do
-          expect(merge_request.mergeable_with_slash_command?(developer, last_diff_sha: mr_sha)).to be_truthy
+          expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_truthy
         end
       end
     end
@@ -1504,8 +1520,8 @@ describe MergeRequest, models: true do
 
   describe '#has_commits?' do
     before do
-      allow(subject.merge_request_diff).to receive(:commits_count).
-        and_return(2)
+      allow(subject.merge_request_diff).to receive(:commits_count)
+        .and_return(2)
     end
 
     it 'returns true when merge request diff has commits' do
@@ -1515,8 +1531,8 @@ describe MergeRequest, models: true do
 
   describe '#has_no_commits?' do
     before do
-      allow(subject.merge_request_diff).to receive(:commits_count).
-        and_return(0)
+      allow(subject.merge_request_diff).to receive(:commits_count)
+        .and_return(0)
     end
 
     it 'returns true when merge request diff has 0 commits' do
@@ -1574,4 +1590,40 @@ describe MergeRequest, models: true do
       end
     end
   end
+
+  describe '#fetch_ref' do
+    it 'sets "ref_fetched" flag to true' do
+      subject.update!(ref_fetched: nil)
+
+      subject.fetch_ref
+
+      expect(subject.reload.ref_fetched).to be_truthy
+    end
+  end
+
+  describe '#ref_fetched?' do
+    it 'does not perform git operation when value is cached' do
+      subject.ref_fetched = true
+
+      expect_any_instance_of(Repository).not_to receive(:ref_exists?)
+      expect(subject.ref_fetched?).to be_truthy
+    end
+
+    it 'caches the value when ref exists but value is not cached' do
+      subject.update!(ref_fetched: nil)
+      allow_any_instance_of(Repository).to receive(:ref_exists?)
+        .and_return(true)
+
+      expect(subject.ref_fetched?).to be_truthy
+      expect(subject.reload.ref_fetched).to be_truthy
+    end
+
+    it 'returns false when ref does not exist' do
+      subject.update!(ref_fetched: nil)
+      allow_any_instance_of(Repository).to receive(:ref_exists?)
+        .and_return(false)
+
+      expect(subject.ref_fetched?).to be_falsey
+    end
+  end
 end
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index aa1ce89ffd7f713ad7d7c43f20b7b33fe8df056d..45953023a366caf935ea39279c07724bfb8e85a7 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -144,35 +144,6 @@ describe Milestone, models: true do
     end
   end
 
-  describe '#sort_issues' do
-    let(:milestone) { create(:milestone) }
-
-    let(:issue1) { create(:issue, milestone: milestone, position: 1) }
-    let(:issue2) { create(:issue, milestone: milestone, position: 2) }
-    let(:issue3) { create(:issue, milestone: milestone, position: 3) }
-    let(:issue4) { create(:issue, position: 42) }
-
-    it 'sorts the given issues' do
-      milestone.sort_issues([issue3.id, issue2.id, issue1.id])
-
-      issue1.reload
-      issue2.reload
-      issue3.reload
-
-      expect(issue1.position).to eq(3)
-      expect(issue2.position).to eq(2)
-      expect(issue3.position).to eq(1)
-    end
-
-    it 'ignores issues not part of the milestone' do
-      milestone.sort_issues([issue3.id, issue2.id, issue1.id, issue4.id])
-
-      issue4.reload
-
-      expect(issue4.position).to eq(42)
-    end
-  end
-
   describe '.search' do
     let(:milestone) { create(:milestone, title: 'foo', description: 'bar') }
 
@@ -193,13 +164,13 @@ describe Milestone, models: true do
     end
 
     it 'returns milestones with a partially matching description' do
-      expect(described_class.search(milestone.description[0..2])).
-        to eq([milestone])
+      expect(described_class.search(milestone.description[0..2]))
+        .to eq([milestone])
     end
 
     it 'returns milestones with a matching description regardless of the casing' do
-      expect(described_class.search(milestone.description.upcase)).
-        to eq([milestone])
+      expect(described_class.search(milestone.description.upcase))
+        .to eq([milestone])
     end
   end
 
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 145c7ad5770f18c96934e4f7ae64ef3dbe8ff405..62c4ea01ce175e287e0dedbe4d0df1c889b7014b 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -323,10 +323,40 @@ describe Namespace, models: true do
     end
   end
 
+  describe '#users_with_descendants', :nested_groups do
+    let(:user_a) { create(:user) }
+    let(:user_b) { create(:user) }
+
+    let(:group) { create(:group) }
+    let(:nested_group) { create(:group, parent: group) }
+    let(:deep_nested_group) { create(:group, parent: nested_group) }
+
+    it 'returns member users on every nest level without duplication' do
+      group.add_developer(user_a)
+      nested_group.add_developer(user_b)
+      deep_nested_group.add_developer(user_a)
+
+      expect(group.users_with_descendants).to contain_exactly(user_a, user_b)
+      expect(nested_group.users_with_descendants).to contain_exactly(user_a, user_b)
+      expect(deep_nested_group.users_with_descendants).to contain_exactly(user_a)
+    end
+  end
+
+  describe '#soft_delete_without_removing_associations' do
+    let(:project1) { create(:project_empty_repo, namespace: namespace) }
+
+    it 'updates the deleted_at timestamp but preserves projects' do
+      namespace.soft_delete_without_removing_associations
+
+      expect(Project.all).to include(project1)
+      expect(namespace.deleted_at).not_to be_nil
+    end
+  end
+
   describe '#user_ids_for_project_authorizations' do
     it 'returns the user IDs for which to refresh authorizations' do
-      expect(namespace.user_ids_for_project_authorizations).
-        to eq([namespace.owner_id])
+      expect(namespace.user_ids_for_project_authorizations)
+        .to eq([namespace.owner_id])
     end
   end
 
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index d4d4fc86343ecf3861fc4073ecbc25498f253854..e2b80cb6e61b62ea098bf8bac125d08ce515d780 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -152,8 +152,8 @@ describe Note, models: true do
     let!(:note2) { create(:note_on_issue) }
 
     it "reads the rendered note body from the cache" do
-      expect(Banzai::Renderer).to receive(:cache_collection_render).
-        with([{
+      expect(Banzai::Renderer).to receive(:cache_collection_render)
+        .with([{
           text: note1.note,
           context: {
             skip_project_check: false,
@@ -164,8 +164,8 @@ describe Note, models: true do
           }
         }]).and_call_original
 
-      expect(Banzai::Renderer).to receive(:cache_collection_render).
-        with([{
+      expect(Banzai::Renderer).to receive(:cache_collection_render)
+        .with([{
           text: note2.note,
           context: {
             skip_project_check: false,
@@ -406,8 +406,8 @@ describe Note, models: true 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)
+        expect(Banzai::Renderer).to receive(:cacheless_render_field)
+          .with(note, :note, { skip_project_check: false }).and_return(html)
 
         note.save
       end
@@ -421,8 +421,8 @@ describe Note, models: true 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)
+        expect(Banzai::Renderer).to receive(:cacheless_render_field)
+          .with(note, :note, { skip_project_check: true }).and_return(html)
 
         note.save
       end
diff --git a/spec/models/project_authorization_spec.rb b/spec/models/project_authorization_spec.rb
index cd0a4a94809082df692c1b7d2e4456e8d3bd7cd2..ee6bdc39c8cc4081a582a24bee5c3f88272083a2 100644
--- a/spec/models/project_authorization_spec.rb
+++ b/spec/models/project_authorization_spec.rb
@@ -7,8 +7,8 @@ describe ProjectAuthorization do
 
   describe '.insert_authorizations' do
     it 'inserts the authorizations' do
-      described_class.
-        insert_authorizations([[user.id, project1.id, Gitlab::Access::MASTER]])
+      described_class
+        .insert_authorizations([[user.id, project1.id, Gitlab::Access::MASTER]])
 
       expect(user.project_authorizations.count).to eq(1)
     end
diff --git a/spec/models/project_feature_spec.rb b/spec/models/project_feature_spec.rb
index 09a4448d387ad4e0f84599f3fcec9d245083e42e..580c83c12c0e34a2c9a08de9f8b3207ca35ee77d 100644
--- a/spec/models/project_feature_spec.rb
+++ b/spec/models/project_feature_spec.rb
@@ -4,6 +4,18 @@ describe ProjectFeature do
   let(:project) { create(:empty_project) }
   let(:user) { create(:user) }
 
+  describe '.quoted_access_level_column' do
+    it 'returns the table name and quoted column name for a feature' do
+      expected = if Gitlab::Database.postgresql?
+                   '"project_features"."issues_access_level"'
+                 else
+                   '`project_features`.`issues_access_level`'
+                 end
+
+      expect(described_class.quoted_access_level_column(:issues)).to eq(expected)
+    end
+  end
+
   describe '#feature_available?' do
     let(:features) { %w(issues wiki builds merge_requests snippets repository) }
 
diff --git a/spec/models/project_group_link_spec.rb b/spec/models/project_group_link_spec.rb
index 4161b9158b1ac6693a50e8a1d003ff51cb844485..d68d8b719cdd5a6abb2ae14e47e44c7638fa7be2 100644
--- a/spec/models/project_group_link_spec.rb
+++ b/spec/models/project_group_link_spec.rb
@@ -2,8 +2,8 @@ require 'spec_helper'
 
 describe ProjectGroupLink do
   describe "Associations" do
-    it { should belong_to(:group) }
-    it { should belong_to(:project) }
+    it { is_expected.to belong_to(:group) }
+    it { is_expected.to belong_to(:project) }
   end
 
   describe "Validation" do
@@ -12,10 +12,10 @@ describe ProjectGroupLink do
     let(:project) { create(:project, group: group) }
     let!(:project_group_link) { create(:project_group_link, project: project) }
 
-    it { should validate_presence_of(:project_id) }
-    it { should validate_uniqueness_of(:group_id).scoped_to(:project_id).with_message(/already shared/) }
-    it { should validate_presence_of(:group) }
-    it { should validate_presence_of(:group_access) }
+    it { is_expected.to validate_presence_of(:project_id) }
+    it { is_expected.to validate_uniqueness_of(:group_id).scoped_to(:project_id).with_message(/already shared/) }
+    it { is_expected.to validate_presence_of(:group) }
+    it { is_expected.to validate_presence_of(:group_access) }
 
     it "doesn't allow a project to be shared with the group it is in" do
       project_group_link.group = group
diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb
index e62fd69e5672c9694a23f783a64b65bdc2878885..7b1a554d1fb53b15665b6b02bb4c49c798d31b97 100644
--- a/spec/models/project_services/bamboo_service_spec.rb
+++ b/spec/models/project_services/bamboo_service_spec.rb
@@ -217,13 +217,13 @@ describe BambooService, models: true, caching: true do
   end
 
   def stub_request(status: 200, body: nil)
-    bamboo_full_url = 'http://mic:password@gitlab.com/bamboo/rest/api/latest/result?label=123&os_authType=basic'
+    bamboo_full_url = 'http://gitlab.com/bamboo/rest/api/latest/result?label=123&os_authType=basic'
 
     WebMock.stub_request(:get, bamboo_full_url).to_return(
       status: status,
       headers: { 'Content-Type' => 'application/json' },
       body: body
-    )
+    ).with(basic_auth: %w(mic password))
   end
 
   def bamboo_response(result_key: 42, build_state: 'success', size: 1)
diff --git a/spec/models/project_services/campfire_service_spec.rb b/spec/models/project_services/campfire_service_spec.rb
index de55627dd2705a2f513b6c9625943c6d86a8567a..56ff35961909338bd8e5fe563e0a5e89f34cfe86 100644
--- a/spec/models/project_services/campfire_service_spec.rb
+++ b/spec/models/project_services/campfire_service_spec.rb
@@ -39,21 +39,22 @@ describe CampfireService, models: true do
         room: 'test-room'
       )
       @sample_data = Gitlab::DataBuilder::Push.build_sample(project, user)
-      @rooms_url = 'https://verySecret:X@project-name.campfirenow.com/rooms.json'
+      @rooms_url = 'https://project-name.campfirenow.com/rooms.json'
+      @auth = %w(verySecret X)
       @headers = { 'Content-Type' => 'application/json; charset=utf-8' }
     end
 
     it "calls Campfire API to get a list of rooms and speak in a room" do
       # make sure a valid list of rooms is returned
       body = File.read(Rails.root + 'spec/fixtures/project_services/campfire/rooms.json')
-      WebMock.stub_request(:get, @rooms_url).to_return(
+      WebMock.stub_request(:get, @rooms_url).with(basic_auth: @auth).to_return(
         body: body,
         status: 200,
         headers: @headers
       )
       # stub the speak request with the room id found in the previous request's response
-      speak_url = 'https://verySecret:X@project-name.campfirenow.com/room/123/speak.json'
-      WebMock.stub_request(:post, speak_url)
+      speak_url = 'https://project-name.campfirenow.com/room/123/speak.json'
+      WebMock.stub_request(:post, speak_url).with(basic_auth: @auth)
 
       @campfire_service.execute(@sample_data)
 
@@ -66,7 +67,7 @@ describe CampfireService, models: true do
     it "calls Campfire API to get a list of rooms but shouldn't speak in a room" do
       # return a list of rooms that do not contain a room named 'test-room'
       body = File.read(Rails.root + 'spec/fixtures/project_services/campfire/rooms2.json')
-      WebMock.stub_request(:get, @rooms_url).to_return(
+      WebMock.stub_request(:get, @rooms_url).with(basic_auth: @auth).to_return(
         body: body,
         status: 200,
         headers: @headers
diff --git a/spec/models/project_services/chat_message/pipeline_message_spec.rb b/spec/models/project_services/chat_message/pipeline_message_spec.rb
index 7d2599dc70337ed0bb29c4528409b231534a3e68..43b02568cb98aa80239f5819af14c8a36b9ee03b 100644
--- a/spec/models/project_services/chat_message/pipeline_message_spec.rb
+++ b/spec/models/project_services/chat_message/pipeline_message_spec.rb
@@ -62,7 +62,7 @@ describe ChatMessage::PipelineMessage do
     def build_message(status_text = status, name = user[:name])
       "<http://example.gitlab.com|project_name>:" \
         " Pipeline <http://example.gitlab.com/pipelines/123|#123>" \
-        " of branch `<http://example.gitlab.com/commits/develop|develop>`" \
+        " of branch <http://example.gitlab.com/commits/develop|develop>" \
         " by #{name} #{status_text} in 02:00:10"
     end
   end
@@ -81,7 +81,7 @@ describe ChatMessage::PipelineMessage do
         expect(subject.pretext).to be_empty
         expect(subject.attachments).to eq(message)
         expect(subject.activity).to eq({
-          title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of branch `[develop](http://example.gitlab.com/commits/develop)` by hacker passed',
+          title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of branch [develop](http://example.gitlab.com/commits/develop) by hacker passed',
           subtitle: 'in [project_name](http://example.gitlab.com)',
           text: 'in 02:00:10',
           image: ''
@@ -98,7 +98,7 @@ describe ChatMessage::PipelineMessage do
         expect(subject.pretext).to be_empty
         expect(subject.attachments).to eq(message)
         expect(subject.activity).to eq({
-          title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of branch `[develop](http://example.gitlab.com/commits/develop)` by hacker failed',
+          title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of branch [develop](http://example.gitlab.com/commits/develop) by hacker failed',
           subtitle: 'in [project_name](http://example.gitlab.com)',
           text: 'in 02:00:10',
           image: ''
@@ -113,7 +113,7 @@ describe ChatMessage::PipelineMessage do
           expect(subject.pretext).to be_empty
           expect(subject.attachments).to eq(message)
           expect(subject.activity).to eq({
-            title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of branch `[develop](http://example.gitlab.com/commits/develop)` by API failed',
+            title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of branch [develop](http://example.gitlab.com/commits/develop) by API failed',
             subtitle: 'in [project_name](http://example.gitlab.com)',
             text: 'in 02:00:10',
             image: ''
@@ -125,7 +125,7 @@ describe ChatMessage::PipelineMessage do
     def build_markdown_message(status_text = status, name = user[:name])
       "[project_name](http://example.gitlab.com):" \
         " Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
-        " of branch `[develop](http://example.gitlab.com/commits/develop)`" \
+        " of branch [develop](http://example.gitlab.com/commits/develop)" \
         " by #{name} #{status_text} in 02:00:10"
     end
   end
diff --git a/spec/models/project_services/chat_message/push_message_spec.rb b/spec/models/project_services/chat_message/push_message_spec.rb
index e38117b75f6ca26b1ae6ea6686bee9eb88e96efc..c794f659c4175a3ae8d3a41b0c56275601612db1 100644
--- a/spec/models/project_services/chat_message/push_message_spec.rb
+++ b/spec/models/project_services/chat_message/push_message_spec.rb
@@ -28,7 +28,7 @@ describe ChatMessage::PushMessage, models: true do
     context 'without markdown' do
       it 'returns a message regarding pushes' do
         expect(subject.pretext).to eq(
-          'test.user pushed to branch `<http://url.com/commits/master|master>` of '\
+          'test.user pushed to branch <http://url.com/commits/master|master> of '\
             '<http://url.com|project_name> (<http://url.com/compare/before...after|Compare changes>)')
         expect(subject.attachments).to eq([{
           text: "<http://url1.com|abcdefgh>: message1 - author1\n\n"\
@@ -45,7 +45,7 @@ describe ChatMessage::PushMessage, models: true do
 
       it 'returns a message regarding pushes' do
         expect(subject.pretext).to eq(
-          'test.user pushed to branch `[master](http://url.com/commits/master)` of [project_name](http://url.com) ([Compare changes](http://url.com/compare/before...after))')
+          'test.user pushed to branch [master](http://url.com/commits/master) of [project_name](http://url.com) ([Compare changes](http://url.com/compare/before...after))')
         expect(subject.attachments).to eq(
           "[abcdefgh](http://url1.com): message1 - author1\n\n[12345678](http://url2.com): message2 - author2")
         expect(subject.activity).to eq({
@@ -74,7 +74,7 @@ describe ChatMessage::PushMessage, models: true do
     context 'without markdown' do
       it 'returns a message regarding pushes' do
         expect(subject.pretext).to eq('test.user pushed new tag ' \
-          '`<http://url.com/commits/new_tag|new_tag>` to ' \
+          '<http://url.com/commits/new_tag|new_tag> to ' \
           '<http://url.com|project_name>')
         expect(subject.attachments).to be_empty
       end
@@ -87,7 +87,7 @@ describe ChatMessage::PushMessage, models: true do
 
       it 'returns a message regarding pushes' do
         expect(subject.pretext).to eq(
-          'test.user pushed new tag `[new_tag](http://url.com/commits/new_tag)` to [project_name](http://url.com)')
+          'test.user pushed new tag [new_tag](http://url.com/commits/new_tag) to [project_name](http://url.com)')
         expect(subject.attachments).to be_empty
         expect(subject.activity).to eq({
           title: 'test.user created tag',
@@ -107,7 +107,7 @@ describe ChatMessage::PushMessage, models: true do
     context 'without markdown' do
       it 'returns a message regarding a new branch' do
         expect(subject.pretext).to eq(
-          'test.user pushed new branch `<http://url.com/commits/master|master>` to '\
+          'test.user pushed new branch <http://url.com/commits/master|master> to '\
             '<http://url.com|project_name>')
         expect(subject.attachments).to be_empty
       end
@@ -120,7 +120,7 @@ describe ChatMessage::PushMessage, models: true do
 
       it 'returns a message regarding a new branch' do
         expect(subject.pretext).to eq(
-          'test.user pushed new branch `[master](http://url.com/commits/master)` to [project_name](http://url.com)')
+          'test.user pushed new branch [master](http://url.com/commits/master) to [project_name](http://url.com)')
         expect(subject.attachments).to be_empty
         expect(subject.activity).to eq({
           title: 'test.user created branch',
@@ -140,7 +140,7 @@ describe ChatMessage::PushMessage, models: true do
     context 'without markdown' do
       it 'returns a message regarding a removed branch' do
         expect(subject.pretext).to eq(
-          'test.user removed branch `master` from <http://url.com|project_name>')
+          'test.user removed branch master from <http://url.com|project_name>')
         expect(subject.attachments).to be_empty
       end
     end
@@ -152,7 +152,7 @@ describe ChatMessage::PushMessage, models: true do
 
       it 'returns a message regarding a removed branch' do
         expect(subject.pretext).to eq(
-          'test.user removed branch `master` from [project_name](http://url.com)')
+          'test.user removed branch master from [project_name](http://url.com)')
         expect(subject.attachments).to be_empty
         expect(subject.activity).to eq({
           title: 'test.user removed branch',
diff --git a/spec/models/project_services/external_wiki_service_spec.rb b/spec/models/project_services/external_wiki_service_spec.rb
index 291fc645a1c9bbf220052866d01f6fc513011921..ef10df9e092fe770d94238bec3378bf5e5127f77 100644
--- a/spec/models/project_services/external_wiki_service_spec.rb
+++ b/spec/models/project_services/external_wiki_service_spec.rb
@@ -3,8 +3,8 @@ require 'spec_helper'
 describe ExternalWikiService, models: true do
   include ExternalWikiHelper
   describe "Associations" do
-    it { should belong_to :project }
-    it { should have_one :service_hook }
+    it { is_expected.to belong_to :project }
+    it { is_expected.to have_one :service_hook }
   end
 
   describe 'Validations' do
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index e2b8226124f000039d6a208ba832a4c69ac49ed4..4a1de76f0995c4aa4b97c5f48dd30cbfc26d7d6e 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -64,12 +64,12 @@ describe JiraService, models: true do
     end
   end
 
-  describe '#reference_pattern' do
+  describe '.reference_pattern' do
     it_behaves_like 'allows project key on reference pattern'
 
     it 'does not allow # on the code' do
-      expect(subject.reference_pattern.match('#123')).to be_nil
-      expect(subject.reference_pattern.match('1#23#12')).to be_nil
+      expect(described_class.reference_pattern.match('#123')).to be_nil
+      expect(described_class.reference_pattern.match('1#23#12')).to be_nil
     end
   end
 
@@ -106,15 +106,15 @@ describe JiraService, models: true do
 
       @jira_service.save
 
-      project_issues_url = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/issue/JIRA-123'
-      @transitions_url   = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/issue/JIRA-123/transitions'
-      @comment_url       = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/issue/JIRA-123/comment'
-      @remote_link_url   = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/issue/JIRA-123/remotelink'
+      project_issues_url = 'http://jira.example.com/rest/api/2/issue/JIRA-123'
+      @transitions_url   = 'http://jira.example.com/rest/api/2/issue/JIRA-123/transitions'
+      @comment_url       = 'http://jira.example.com/rest/api/2/issue/JIRA-123/comment'
+      @remote_link_url   = 'http://jira.example.com/rest/api/2/issue/JIRA-123/remotelink'
 
-      WebMock.stub_request(:get, project_issues_url)
-      WebMock.stub_request(:post, @transitions_url)
-      WebMock.stub_request(:post, @comment_url)
-      WebMock.stub_request(:post, @remote_link_url)
+      WebMock.stub_request(:get, project_issues_url).with(basic_auth: %w(gitlab_jira_username gitlab_jira_password))
+      WebMock.stub_request(:post, @transitions_url).with(basic_auth: %w(gitlab_jira_username gitlab_jira_password))
+      WebMock.stub_request(:post, @comment_url).with(basic_auth: %w(gitlab_jira_username gitlab_jira_password))
+      WebMock.stub_request(:post, @remote_link_url).with(basic_auth: %w(gitlab_jira_username gitlab_jira_password))
     end
 
     it "calls JIRA API" do
@@ -202,9 +202,9 @@ describe JiraService, models: true do
     end
 
     def test_settings(api_url)
-      project_url = "http://jira_username:jira_password@#{api_url}/rest/api/2/project/GitLabProject"
+      project_url = "http://#{api_url}/rest/api/2/project/GitLabProject"
 
-      WebMock.stub_request(:get, project_url)
+      WebMock.stub_request(:get, project_url).with(basic_auth: %w(jira_username jira_password))
 
       jira_service.test_settings
     end
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 f9531be5d251e8e32e9bd9af2ddce15415b2ac35..fa38d23e82faab94c37a8f3e155953007a9dd053 100644
--- a/spec/models/project_services/mattermost_slash_commands_service_spec.rb
+++ b/spec/models/project_services/mattermost_slash_commands_service_spec.rb
@@ -11,8 +11,8 @@ describe MattermostSlashCommandsService, :models do
     before do
       Mattermost::Session.base_uri("http://mattermost.example.com")
 
-      allow_any_instance_of(Mattermost::Client).to receive(:with_session).
-        and_yield(Mattermost::Session.new(nil))
+      allow_any_instance_of(Mattermost::Client).to receive(:with_session)
+        .and_yield(Mattermost::Session.new(nil))
     end
 
     describe '#configure' do
@@ -24,8 +24,8 @@ describe MattermostSlashCommandsService, :models do
 
       context 'the requests succeeds' do
         before do
-          stub_request(:post, 'http://mattermost.example.com/api/v3/teams/abc/commands/create').
-            with(body: {
+          stub_request(:post, 'http://mattermost.example.com/api/v3/teams/abc/commands/create')
+            .with(body: {
               team_id: 'abc',
               trigger: 'gitlab',
               url: 'http://trigger.url',
@@ -37,8 +37,8 @@ describe MattermostSlashCommandsService, :models do
               display_name: "GitLab / #{project.name_with_namespace}",
               method: 'P',
               username: 'GitLab'
-            }.to_json).
-            to_return(
+            }.to_json)
+            .to_return(
               status: 200,
               headers: { 'Content-Type' => 'application/json' },
               body: { token: 'token' }.to_json
@@ -58,8 +58,8 @@ describe MattermostSlashCommandsService, :models do
 
       context 'an error is received' do
         before do
-          stub_request(:post, 'http://mattermost.example.com/api/v3/teams/abc/commands/create').
-            to_return(
+          stub_request(:post, 'http://mattermost.example.com/api/v3/teams/abc/commands/create')
+            .to_return(
               status: 500,
               headers: { 'Content-Type' => 'application/json' },
               body: {
@@ -88,8 +88,8 @@ describe MattermostSlashCommandsService, :models do
 
       context 'the requests succeeds' do
         before do
-          stub_request(:get, 'http://mattermost.example.com/api/v3/teams/all').
-            to_return(
+          stub_request(:get, 'http://mattermost.example.com/api/v3/teams/all')
+            .to_return(
               status: 200,
               headers: { 'Content-Type' => 'application/json' },
               body: { 'list' => true }.to_json
@@ -103,8 +103,8 @@ describe MattermostSlashCommandsService, :models do
 
       context 'an error is received' do
         before do
-          stub_request(:get, 'http://mattermost.example.com/api/v3/teams/all').
-            to_return(
+          stub_request(:get, 'http://mattermost.example.com/api/v3/teams/all')
+            .to_return(
               status: 500,
               headers: { 'Content-Type' => 'application/json' },
               body: {
diff --git a/spec/models/project_services/prometheus_service_spec.rb b/spec/models/project_services/prometheus_service_spec.rb
index 71b5373216450afd21c12b470ff05b38cb6990cf..37f23b1243c41c5f14fe6a4615dc9b416bc9d1f2 100644
--- a/spec/models/project_services/prometheus_service_spec.rb
+++ b/spec/models/project_services/prometheus_service_spec.rb
@@ -65,13 +65,13 @@ describe PrometheusService, models: true, caching: true do
       end
 
       it 'returns reactive data' do
-        is_expected.to eq(prometheus_data)
+        is_expected.to eq(prometheus_metrics_data)
       end
     end
   end
 
   describe '#deployment_metrics' do
-    let(:deployment) { build_stubbed(:deployment)}
+    let(:deployment) { build_stubbed(:deployment) }
     let(:deployment_query) { Gitlab::Prometheus::Queries::DeploymentQuery }
 
     around do |example|
@@ -80,13 +80,16 @@ describe PrometheusService, models: true, caching: true do
 
     context 'with valid data' do
       subject { service.deployment_metrics(deployment) }
+      let(:fake_deployment_time) { 10 }
 
       before do
         stub_reactive_cache(service, prometheus_data, deployment_query, deployment.id)
       end
 
       it 'returns reactive data' do
-        is_expected.to eq(prometheus_data.merge(deployment_time: deployment.created_at.to_i))
+        expect(deployment).to receive(:created_at).and_return(fake_deployment_time)
+
+        expect(subject).to eq(prometheus_metrics_data.merge(deployment_time: fake_deployment_time))
       end
     end
   end
@@ -116,6 +119,7 @@ describe PrometheusService, models: true, caching: true do
       end
 
       it { expect(subject.to_json).to eq(prometheus_data.to_json) }
+      it { expect(subject.to_json).to eq(prometheus_data.to_json) }
     end
 
     [404, 500].each do |status|
diff --git a/spec/models/project_services/redmine_service_spec.rb b/spec/models/project_services/redmine_service_spec.rb
index 6631d9040b13fb674b2493622aa8f2d31b724a74..441b3f896cab206527917c17308c9ed1c343992a 100644
--- a/spec/models/project_services/redmine_service_spec.rb
+++ b/spec/models/project_services/redmine_service_spec.rb
@@ -31,11 +31,11 @@ describe RedmineService, models: true do
     end
   end
 
-  describe '#reference_pattern' do
+  describe '.reference_pattern' do
     it_behaves_like 'allows project key on reference pattern'
 
     it 'does allow # on the reference' do
-      expect(subject.reference_pattern.match('#123')[:issue]).to eq('123')
+      expect(described_class.reference_pattern.match('#123')[:issue]).to eq('123')
     end
   end
 end
diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb
index 7349eb4149a51ea843eadff9fa5861c83be30790..6b0040985109239bbf84a3cf403a6032bb7e2264 100644
--- a/spec/models/project_services/teamcity_service_spec.rb
+++ b/spec/models/project_services/teamcity_service_spec.rb
@@ -205,10 +205,12 @@ describe TeamcityService, models: true, caching: true do
   end
 
   def stub_request(status: 200, body: nil, build_status: 'success')
-    teamcity_full_url = 'http://mic:password@gitlab.com/teamcity/httpAuth/app/rest/builds/branch:unspecified:any,number:123'
+    teamcity_full_url = 'http://gitlab.com/teamcity/httpAuth/app/rest/builds/branch:unspecified:any,number:123'
+    auth = %w(mic password)
+
     body ||= %Q({"build":{"status":"#{build_status}","id":"666"}})
 
-    WebMock.stub_request(:get, teamcity_full_url).to_return(
+    WebMock.stub_request(:get, teamcity_full_url).with(basic_auth: auth).to_return(
       status: status,
       headers: { 'Content-Type' => 'application/json' },
       body: body
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 63333b7af1f28496ae033ed0c244ebc4573e2e1a..f50b4aea41120ff3a6fb63452e68a363fb4e5f20 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -143,6 +143,10 @@ describe Project, models: true do
 
     it { is_expected.to validate_length_of(:description).is_at_most(2000) }
 
+    it { is_expected.to validate_length_of(:ci_config_path).is_at_most(255) }
+    it { is_expected.to allow_value('').for(:ci_config_path) }
+    it { is_expected.not_to allow_value('test/../foo').for(:ci_config_path) }
+
     it { is_expected.to validate_presence_of(:creator) }
 
     it { is_expected.to validate_presence_of(:namespace) }
@@ -284,15 +288,6 @@ describe Project, models: true do
     end
   end
 
-  describe 'default_scope' do
-    it 'excludes projects pending deletion from the results' do
-      project = create(:empty_project)
-      create(:empty_project, pending_delete: true)
-
-      expect(Project.all).to eq [project]
-    end
-  end
-
   describe 'project token' do
     it 'sets an random token if none provided' do
       project = FactoryGirl.create :empty_project, runners_token: ''
@@ -832,13 +827,13 @@ describe Project, models: true do
 
       let(:avatar_path) { "/#{project.full_path}/avatar" }
 
-      it { should eq "http://#{Gitlab.config.gitlab.host}#{avatar_path}" }
+      it { is_expected.to eq "http://#{Gitlab.config.gitlab.host}#{avatar_path}" }
     end
 
     context 'when git repo is empty' do
       let(:project) { create(:empty_project) }
 
-      it { should eq nil }
+      it { is_expected.to eq nil }
     end
   end
 
@@ -1179,6 +1174,16 @@ describe Project, models: true do
 
       expect(relation.search(project.namespace.name)).to eq([project])
     end
+
+    describe 'with pending_delete project' do
+      let(:pending_delete_project) { create(:empty_project, pending_delete: true) }
+
+      it 'shows pending deletion project' do
+        search_result = described_class.search(pending_delete_project.name)
+
+        expect(search_result).to eq([pending_delete_project])
+      end
+    end
   end
 
   describe '#rename_repo' do
@@ -1195,26 +1200,28 @@ describe Project, models: true do
     it 'renames a repository' do
       stub_container_registry_config(enabled: false)
 
-      expect(gitlab_shell).to receive(:mv_repository).
-        ordered.
-        with(project.repository_storage_path, "#{project.namespace.full_path}/foo", "#{project.full_path}").
-        and_return(true)
+      expect(gitlab_shell).to receive(:mv_repository)
+        .ordered
+        .with(project.repository_storage_path, "#{project.namespace.full_path}/foo", "#{project.full_path}")
+        .and_return(true)
 
-      expect(gitlab_shell).to receive(:mv_repository).
-        ordered.
-        with(project.repository_storage_path, "#{project.namespace.full_path}/foo.wiki", "#{project.full_path}.wiki").
-        and_return(true)
+      expect(gitlab_shell).to receive(:mv_repository)
+        .ordered
+        .with(project.repository_storage_path, "#{project.namespace.full_path}/foo.wiki", "#{project.full_path}.wiki")
+        .and_return(true)
 
-      expect_any_instance_of(SystemHooksService).
-        to receive(:execute_hooks_for).
-        with(project, :rename)
+      expect_any_instance_of(SystemHooksService)
+        .to receive(:execute_hooks_for)
+        .with(project, :rename)
 
-      expect_any_instance_of(Gitlab::UploadsTransfer).
-        to receive(:rename_project).
-        with('foo', project.path, project.namespace.full_path)
+      expect_any_instance_of(Gitlab::UploadsTransfer)
+        .to receive(:rename_project)
+        .with('foo', project.path, project.namespace.full_path)
 
       expect(project).to receive(:expire_caches_before_rename)
 
+      expect(project).to receive(:expires_full_path_cache)
+
       project.rename_repo
     end
 
@@ -1239,13 +1246,13 @@ describe Project, models: true do
     let(:wiki)    { double(:wiki, exists?: true) }
 
     it 'expires the caches of the repository and wiki' do
-      allow(Repository).to receive(:new).
-        with('foo', project).
-        and_return(repo)
+      allow(Repository).to receive(:new)
+        .with('foo', project)
+        .and_return(repo)
 
-      allow(Repository).to receive(:new).
-        with('foo.wiki', project).
-        and_return(wiki)
+      allow(Repository).to receive(:new)
+        .with('foo.wiki', project)
+        .and_return(wiki)
 
       expect(repo).to receive(:before_delete)
       expect(wiki).to receive(:before_delete)
@@ -1296,9 +1303,9 @@ describe Project, models: true do
 
     context 'using a regular repository' do
       it 'creates the repository' do
-        expect(shell).to receive(:add_repository).
-          with(project.repository_storage_path, project.path_with_namespace).
-          and_return(true)
+        expect(shell).to receive(:add_repository)
+          .with(project.repository_storage_path, project.path_with_namespace)
+          .and_return(true)
 
         expect(project.repository).to receive(:after_create)
 
@@ -1306,9 +1313,9 @@ describe Project, models: true do
       end
 
       it 'adds an error if the repository could not be created' do
-        expect(shell).to receive(:add_repository).
-          with(project.repository_storage_path, project.path_with_namespace).
-          and_return(false)
+        expect(shell).to receive(:add_repository)
+          .with(project.repository_storage_path, project.path_with_namespace)
+          .and_return(false)
 
         expect(project.repository).not_to receive(:after_create)
 
@@ -1327,6 +1334,50 @@ describe Project, models: true do
     end
   end
 
+  describe '#ensure_repository' do
+    let(:project) { create(:project, :repository) }
+    let(:shell) { Gitlab::Shell.new }
+
+    before do
+      allow(project).to receive(:gitlab_shell).and_return(shell)
+    end
+
+    it 'creates the repository if it not exist' do
+      allow(project).to receive(:repository_exists?)
+        .and_return(false)
+
+      allow(shell).to receive(:add_repository)
+        .with(project.repository_storage_path, project.path_with_namespace)
+        .and_return(true)
+
+      expect(project).to receive(:create_repository).with(force: true)
+
+      project.ensure_repository
+    end
+
+    it 'does not create the repository if it exists' do
+      allow(project).to receive(:repository_exists?)
+        .and_return(true)
+
+      expect(project).not_to receive(:create_repository)
+
+      project.ensure_repository
+    end
+
+    it 'creates the repository if it is a fork' do
+      expect(project).to receive(:forked?).and_return(true)
+
+      allow(project).to receive(:repository_exists?)
+        .and_return(false)
+
+      expect(shell).to receive(:add_repository)
+        .with(project.repository_storage_path, project.path_with_namespace)
+        .and_return(true)
+
+      project.ensure_repository
+    end
+  end
+
   describe '#user_can_push_to_empty_repo?' do
     let(:project) { create(:empty_project) }
     let(:user)    { create(:user) }
@@ -1457,6 +1508,28 @@ describe Project, models: true do
     end
   end
 
+  describe '#ci_config_path=' do
+    let(:project) { create(:empty_project) }
+
+    it 'sets nil' do
+      project.update!(ci_config_path: nil)
+
+      expect(project.ci_config_path).to be_nil
+    end
+
+    it 'sets a string' do
+      project.update!(ci_config_path: 'foo/.gitlab_ci.yml')
+
+      expect(project.ci_config_path).to eq('foo/.gitlab_ci.yml')
+    end
+
+    it 'sets a string but removes all leading slashes and null characters' do
+      project.update!(ci_config_path: "///f\0oo/\0/.gitlab_ci.yml")
+
+      expect(project.ci_config_path).to eq('foo//.gitlab_ci.yml')
+    end
+  end
+
   describe 'Project import job' do
     let(:project) { create(:empty_project, import_url: generate(:url)) }
 
@@ -1478,6 +1551,40 @@ describe Project, models: true do
     end
   end
 
+  describe 'project import state transitions' do
+    context 'state transition: [:started] => [:finished]' do
+      let(:housekeeping_service) { spy }
+
+      before do
+        allow(Projects::HousekeepingService).to receive(:new) { housekeeping_service }
+      end
+
+      it 'performs housekeeping when an import of a fresh project is completed' do
+        project = create(:project_empty_repo, :import_started, import_type: :github)
+
+        project.import_finish
+
+        expect(housekeeping_service).to have_received(:execute)
+      end
+
+      it 'does not perform housekeeping when project repository does not exist' do
+        project = create(:empty_project, :import_started, import_type: :github)
+
+        project.import_finish
+
+        expect(housekeeping_service).not_to have_received(:execute)
+      end
+
+      it 'does not perform housekeeping when project does not have a valid import type' do
+        project = create(:empty_project, :import_started, import_type: nil)
+
+        project.import_finish
+
+        expect(housekeeping_service).not_to have_received(:execute)
+      end
+    end
+  end
+
   describe '#latest_successful_builds_for' do
     def create_pipeline(status = 'success')
       create(:ci_pipeline, project: project,
@@ -1564,8 +1671,8 @@ describe Project, models: true do
       let(:project) { forked_project_link.forked_to_project }
 
       it 'schedules a RepositoryForkWorker job' do
-        expect(RepositoryForkWorker).to receive(:perform_async).
-          with(project.id, forked_from_project.repository_storage_path,
+        expect(RepositoryForkWorker).to receive(:perform_async)
+          .with(project.id, forked_from_project.repository_storage_path,
               forked_from_project.path_with_namespace, project.namespace.full_path)
 
         project.add_import_job
@@ -2041,15 +2148,15 @@ describe Project, models: true do
       error_message = 'Failed to replace merge_requests because one or more of the new records could not be saved.'\
                       ' Validate fork Source project is not a fork of the target project'
 
-      expect { project.append_or_update_attribute(:merge_requests, [create(:merge_request)]) }.
-        to raise_error(ActiveRecord::RecordNotSaved, error_message)
+      expect { project.append_or_update_attribute(:merge_requests, [create(:merge_request)]) }
+        .to raise_error(ActiveRecord::RecordNotSaved, error_message)
     end
 
     it 'updates the project succesfully' do
       merge_request = create(:merge_request, target_project: project, source_project: project)
 
-      expect { project.append_or_update_attribute(:merge_requests, [merge_request]) }.
-        not_to raise_error
+      expect { project.append_or_update_attribute(:merge_requests, [merge_request]) }
+        .not_to raise_error
     end
   end
 
@@ -2060,4 +2167,36 @@ describe Project, models: true do
       expect(project.last_repository_updated_at.to_i).to eq(project.created_at.to_i)
     end
   end
+
+  describe '.public_or_visible_to_user' do
+    let!(:user) { create(:user) }
+
+    let!(:private_project) do
+      create(:empty_project, :private, creator: user, namespace: user.namespace)
+    end
+
+    let!(:public_project) { create(:empty_project, :public) }
+
+    context 'with a user' do
+      let(:projects) do
+        Project.all.public_or_visible_to_user(user)
+      end
+
+      it 'includes projects the user has access to' do
+        expect(projects).to include(private_project)
+      end
+
+      it 'includes projects the user can see' do
+        expect(projects).to include(public_project)
+      end
+    end
+
+    context 'without a user' do
+      it 'only includes public projects' do
+        projects = Project.all.public_or_visible_to_user
+
+        expect(projects).to eq([public_project])
+      end
+    end
+  end
 end
diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb
index ea3cd5fe10ac17665b4907a840ff5843fb260b79..49f2f8c0ad1445f7b2018297ec53deb67fbcd976 100644
--- a/spec/models/project_team_spec.rb
+++ b/spec/models/project_team_spec.rb
@@ -100,8 +100,8 @@ describe ProjectTeam, models: true do
           group_access: Gitlab::Access::GUEST
         )
 
-        expect(project.team.members).
-          to contain_exactly(group_member.user, project.owner)
+        expect(project.team.members)
+          .to contain_exactly(group_member.user, project.owner)
       end
 
       it 'returns invited members of a group of a specified level' do
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index 3f5f4eea4a12932d849d413a274a5aa70bbc2be8..1f314791479b38e28883d3bc9f78179bf882c196 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -149,15 +149,15 @@ describe ProjectWiki, models: true do
   describe '#find_file' do
     before do
       file = Gollum::File.new(subject.wiki)
-      allow_any_instance_of(Gollum::Wiki).
-                   to receive(:file).with('image.jpg', 'master', true).
-                   and_return(file)
-      allow_any_instance_of(Gollum::File).
-                   to receive(:mime_type).
-                   and_return('image/jpeg')
-      allow_any_instance_of(Gollum::Wiki).
-                   to receive(:file).with('non-existant', 'master', true).
-                   and_return(nil)
+      allow_any_instance_of(Gollum::Wiki)
+                   .to receive(:file).with('image.jpg', 'master', true)
+                   .and_return(file)
+      allow_any_instance_of(Gollum::File)
+                   .to receive(:mime_type)
+                   .and_return('image/jpeg')
+      allow_any_instance_of(Gollum::Wiki)
+                   .to receive(:file).with('non-existant', 'master', true)
+                   .and_return(nil)
     end
 
     after do
@@ -268,9 +268,9 @@ describe ProjectWiki, models: true do
 
   describe '#create_repo!' do
     it 'creates a repository' do
-      expect(subject).to receive(:init_repo).
-        with(subject.path_with_namespace).
-        and_return(true)
+      expect(subject).to receive(:init_repo)
+        .with(subject.path_with_namespace)
+        .and_return(true)
 
       expect(subject.repository).to receive(:after_create)
 
@@ -278,6 +278,24 @@ describe ProjectWiki, models: true do
     end
   end
 
+  describe '#ensure_repository' do
+    it 'creates the repository if it not exist' do
+      allow(subject).to receive(:repository_exists?).and_return(false)
+
+      expect(subject).to receive(:create_repo!)
+
+      subject.ensure_repository
+    end
+
+    it 'does not create the repository if it exists' do
+      allow(subject).to receive(:repository_exists?).and_return(true)
+
+      expect(subject).not_to receive(:create_repo!)
+
+      subject.ensure_repository
+    end
+  end
+
   describe '#hook_attrs' do
     it 'returns a hash with values' do
       expect(subject.hook_attrs).to be_a Hash
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index a6d4d92c45017a6980f11ef9ace53320a408ef91..af305e9b23473348e242f889d19b117dfdd4db31 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -111,8 +111,8 @@ describe Repository, models: true do
 
   describe '#ref_name_for_sha' do
     it 'returns the ref' do
-      allow(repository.raw_repository).to receive(:ref_name_for_sha).
-        and_return('refs/environments/production/77')
+      allow(repository.raw_repository).to receive(:ref_name_for_sha)
+        .and_return('refs/environments/production/77')
 
       expect(repository.ref_name_for_sha('bla', '0' * 40)).to eq 'refs/environments/production/77'
     end
@@ -347,6 +347,17 @@ describe Repository, models: true do
       expect(blob.data).to eq('Changelog!')
     end
 
+    it 'creates new file and dir when file_path has a forward slash' do
+      expect do
+        repository.create_file(user, 'new_dir/new_file.txt', 'File!',
+                               message: 'Create new_file with new_dir',
+                               branch_name: 'master')
+      end.to change { repository.commits('master').count }.by(1)
+
+      expect(repository.tree('master', 'new_dir').path).to eq('new_dir')
+      expect(repository.blob_at('master', 'new_dir/new_file.txt').data).to eq('File!')
+    end
+
     it 'respects the autocrlf setting' do
       repository.create_file(user, 'hello.txt', "Hello,\r\nWorld",
                              message: 'Add hello world',
@@ -593,8 +604,8 @@ describe Repository, models: true do
         user, 'LICENSE', 'Copyright!',
         message: 'Add LICENSE', branch_name: 'master')
 
-      allow(repository).to receive(:file_on_head).
-        and_raise(Rugged::ReferenceError)
+      allow(repository).to receive(:file_on_head)
+        .and_raise(Rugged::ReferenceError)
 
       expect(repository.license_blob).to be_nil
     end
@@ -779,8 +790,8 @@ describe Repository, models: true do
 
     context 'when pre hooks were successful' do
       it 'runs without errors' do
-        expect_any_instance_of(GitHooksService).to receive(:execute).
-          with(user, project.repository.path_to_repo, old_rev, blank_sha, 'refs/heads/feature')
+        expect_any_instance_of(GitHooksService).to receive(:execute)
+          .with(user, project, old_rev, blank_sha, 'refs/heads/feature')
 
         expect { repository.rm_branch(user, 'feature') }.not_to raise_error
       end
@@ -822,14 +833,9 @@ describe Repository, models: true do
       before do
         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)
+        expect(service).to receive(:execute)
+          .with(user, project, old_rev, new_rev, 'refs/heads/feature')
+          .and_yield(service).and_return(true)
       end
 
       it 'runs without errors' do
@@ -923,8 +929,8 @@ describe Repository, models: true do
         expect(repository).not_to receive(:expire_emptiness_caches)
         expect(repository).to     receive(:expire_branches_cache)
 
-        GitOperationService.new(user, repository).
-          with_branch('new-feature') do
+        GitOperationService.new(user, repository)
+          .with_branch('new-feature') do
             new_rev
           end
       end
@@ -1007,8 +1013,8 @@ describe Repository, models: true do
       end
 
       it 'does nothing' do
-        expect(repository.raw_repository).not_to receive(:autocrlf=).
-          with(:input)
+        expect(repository.raw_repository).not_to receive(:autocrlf=)
+          .with(:input)
 
         GitOperationService.new(nil, repository).send(:update_autocrlf_option)
       end
@@ -1027,9 +1033,9 @@ describe Repository, models: true do
     end
 
     it 'caches the output' do
-      expect(repository.raw_repository).to receive(:empty?).
-        once.
-        and_return(false)
+      expect(repository.raw_repository).to receive(:empty?)
+        .once
+        .and_return(false)
 
       repository.empty?
       repository.empty?
@@ -1042,9 +1048,9 @@ describe Repository, models: true do
     end
 
     it 'caches the output' do
-      expect(repository.raw_repository).to receive(:root_ref).
-        once.
-        and_return('master')
+      expect(repository.raw_repository).to receive(:root_ref)
+        .once
+        .and_return('master')
 
       repository.root_ref
       repository.root_ref
@@ -1055,9 +1061,9 @@ describe Repository, models: true do
     it 'expires the root reference cache' do
       repository.root_ref
 
-      expect(repository.raw_repository).to receive(:root_ref).
-        once.
-        and_return('foo')
+      expect(repository.raw_repository).to receive(:root_ref)
+        .once
+        .and_return('foo')
 
       repository.expire_root_ref_cache
 
@@ -1071,17 +1077,17 @@ describe Repository, models: true do
     let(:cache) { repository.send(:cache) }
 
     it 'expires the cache for all branches' do
-      expect(cache).to receive(:expire).
-        at_least(repository.branches.length * 2).
-        times
+      expect(cache).to receive(:expire)
+        .at_least(repository.branches.length * 2)
+        .times
 
       repository.expire_branch_cache
     end
 
     it 'expires the cache for all branches when the root branch is given' do
-      expect(cache).to receive(:expire).
-        at_least(repository.branches.length * 2).
-        times
+      expect(cache).to receive(:expire)
+        .at_least(repository.branches.length * 2)
+        .times
 
       repository.expire_branch_cache(repository.root_ref)
     end
@@ -1344,12 +1350,12 @@ describe Repository, models: true do
 
   describe '#after_push_commit' do
     it 'expires statistics caches' do
-      expect(repository).to receive(:expire_statistics_caches).
-        and_call_original
+      expect(repository).to receive(:expire_statistics_caches)
+        .and_call_original
 
-      expect(repository).to receive(:expire_branch_cache).
-        with('master').
-        and_call_original
+      expect(repository).to receive(:expire_branch_cache)
+        .with('master')
+        .and_call_original
 
       repository.after_push_commit('master')
     end
@@ -1434,9 +1440,9 @@ describe Repository, models: true do
 
   describe '#expire_branches_cache' do
     it 'expires the cache' do
-      expect(repository).to receive(:expire_method_caches).
-        with(%i(branch_names branch_count)).
-        and_call_original
+      expect(repository).to receive(:expire_method_caches)
+        .with(%i(branch_names branch_count))
+        .and_call_original
 
       repository.expire_branches_cache
     end
@@ -1444,9 +1450,9 @@ describe Repository, models: true do
 
   describe '#expire_tags_cache' do
     it 'expires the cache' do
-      expect(repository).to receive(:expire_method_caches).
-        with(%i(tag_names tag_count)).
-        and_call_original
+      expect(repository).to receive(:expire_method_caches)
+        .with(%i(tag_names tag_count))
+        .and_call_original
 
       repository.expire_tags_cache
     end
@@ -1457,11 +1463,11 @@ describe Repository, models: true do
       let(:user) { build_stubbed(:user) }
 
       it 'creates the tag using rugged' do
-        expect(repository.rugged.tags).to receive(:create).
-          with('8.5', repository.commit('master').id,
+        expect(repository.rugged.tags).to receive(:create)
+          .with('8.5', repository.commit('master').id,
             hash_including(message: 'foo',
-                           tagger: hash_including(name: user.name, email: user.email))).
-          and_call_original
+                           tagger: hash_including(name: user.name, email: user.email)))
+          .and_call_original
 
         repository.add_tag(user, '8.5', 'master', 'foo')
       end
@@ -1474,12 +1480,12 @@ describe Repository, models: true do
 
       it 'passes commit SHA to pre-receive and update hooks,\
         and tag SHA to post-receive hook' do
-        pre_receive_hook = Gitlab::Git::Hook.new('pre-receive', repository.path_to_repo)
-        update_hook = Gitlab::Git::Hook.new('update', repository.path_to_repo)
-        post_receive_hook = Gitlab::Git::Hook.new('post-receive', repository.path_to_repo)
+        pre_receive_hook = Gitlab::Git::Hook.new('pre-receive', project)
+        update_hook = Gitlab::Git::Hook.new('update', project)
+        post_receive_hook = Gitlab::Git::Hook.new('post-receive', project)
 
-        allow(Gitlab::Git::Hook).to receive(:new).
-          and_return(pre_receive_hook, update_hook, post_receive_hook)
+        allow(Gitlab::Git::Hook).to receive(:new)
+          .and_return(pre_receive_hook, update_hook, post_receive_hook)
 
         allow(pre_receive_hook).to receive(:trigger).and_call_original
         allow(update_hook).to receive(:trigger).and_call_original
@@ -1490,12 +1496,12 @@ describe Repository, models: true do
         commit_sha = repository.commit('master').id
         tag_sha = tag.target
 
-        expect(pre_receive_hook).to have_received(:trigger).
-          with(anything, anything, commit_sha, anything)
-        expect(update_hook).to have_received(:trigger).
-          with(anything, anything, commit_sha, anything)
-        expect(post_receive_hook).to have_received(:trigger).
-          with(anything, anything, tag_sha, anything)
+        expect(pre_receive_hook).to have_received(:trigger)
+          .with(anything, anything, commit_sha, anything)
+        expect(update_hook).to have_received(:trigger)
+          .with(anything, anything, commit_sha, anything)
+        expect(post_receive_hook).to have_received(:trigger)
+          .with(anything, anything, tag_sha, anything)
       end
     end
 
@@ -1529,25 +1535,25 @@ describe Repository, models: true do
 
   describe '#avatar' do
     it 'returns nil if repo does not exist' do
-      expect(repository).to receive(:file_on_head).
-        and_raise(Rugged::ReferenceError)
+      expect(repository).to receive(:file_on_head)
+        .and_raise(Rugged::ReferenceError)
 
       expect(repository.avatar).to eq(nil)
     end
 
     it 'returns the first avatar file found in the repository' do
-      expect(repository).to receive(:file_on_head).
-        with(:avatar).
-        and_return(double(:tree, path: 'logo.png'))
+      expect(repository).to receive(:file_on_head)
+        .with(:avatar)
+        .and_return(double(:tree, path: 'logo.png'))
 
       expect(repository.avatar).to eq('logo.png')
     end
 
     it 'caches the output' do
-      expect(repository).to receive(:file_on_head).
-        with(:avatar).
-        once.
-        and_return(double(:tree, path: 'logo.png'))
+      expect(repository).to receive(:file_on_head)
+        .with(:avatar)
+        .once
+        .and_return(double(:tree, path: 'logo.png'))
 
       2.times { expect(repository.avatar).to eq('logo.png') }
     end
@@ -1607,24 +1613,24 @@ describe Repository, models: true do
 
   describe '#contribution_guide', caching: true do
     it 'returns and caches the output' do
-      expect(repository).to receive(:file_on_head).
-        with(:contributing).
-        and_return(Gitlab::Git::Tree.new(path: 'CONTRIBUTING.md')).
-        once
+      expect(repository).to receive(:file_on_head)
+        .with(:contributing)
+        .and_return(Gitlab::Git::Tree.new(path: 'CONTRIBUTING.md'))
+        .once
 
       2.times do
-        expect(repository.contribution_guide).
-          to be_an_instance_of(Gitlab::Git::Tree)
+        expect(repository.contribution_guide)
+          .to be_an_instance_of(Gitlab::Git::Tree)
       end
     end
   end
 
   describe '#gitignore', caching: true do
     it 'returns and caches the output' do
-      expect(repository).to receive(:file_on_head).
-        with(:gitignore).
-        and_return(Gitlab::Git::Tree.new(path: '.gitignore')).
-        once
+      expect(repository).to receive(:file_on_head)
+        .with(:gitignore)
+        .and_return(Gitlab::Git::Tree.new(path: '.gitignore'))
+        .once
 
       2.times do
         expect(repository.gitignore).to be_an_instance_of(Gitlab::Git::Tree)
@@ -1634,10 +1640,10 @@ describe Repository, models: true do
 
   describe '#koding_yml', caching: true do
     it 'returns and caches the output' do
-      expect(repository).to receive(:file_on_head).
-        with(:koding).
-        and_return(Gitlab::Git::Tree.new(path: '.koding.yml')).
-        once
+      expect(repository).to receive(:file_on_head)
+        .with(:koding)
+        .and_return(Gitlab::Git::Tree.new(path: '.koding.yml'))
+        .once
 
       2.times do
         expect(repository.koding_yml).to be_an_instance_of(Gitlab::Git::Tree)
@@ -1673,8 +1679,8 @@ describe Repository, models: true do
 
   describe '#expire_statistics_caches' do
     it 'expires the caches' do
-      expect(repository).to receive(:expire_method_caches).
-        with(%i(size commit_count))
+      expect(repository).to receive(:expire_method_caches)
+        .with(%i(size commit_count))
 
       repository.expire_statistics_caches
     end
@@ -1691,8 +1697,8 @@ describe Repository, models: true do
 
   describe '#expire_all_method_caches' do
     it 'expires the caches of all methods' do
-      expect(repository).to receive(:expire_method_caches).
-        with(Repository::CACHED_METHODS)
+      expect(repository).to receive(:expire_method_caches)
+        .with(Repository::CACHED_METHODS)
 
       repository.expire_all_method_caches
     end
@@ -1717,8 +1723,8 @@ describe Repository, models: true do
 
     context 'with an existing repository' do
       it 'returns a Gitlab::Git::Tree' do
-        expect(repository.file_on_head(:readme)).
-          to be_an_instance_of(Gitlab::Git::Tree)
+        expect(repository.file_on_head(:readme))
+          .to be_an_instance_of(Gitlab::Git::Tree)
       end
     end
   end
@@ -1856,8 +1862,8 @@ describe Repository, models: true do
 
   describe '#refresh_method_caches' do
     it 'refreshes the caches of the given types' do
-      expect(repository).to receive(:expire_method_caches).
-        with(%i(rendered_readme license_blob license_key license))
+      expect(repository).to receive(:expire_method_caches)
+        .with(%i(rendered_readme license_blob license_key license))
 
       expect(repository).to receive(:rendered_readme)
       expect(repository).to receive(:license_blob)
diff --git a/spec/models/upload_spec.rb b/spec/models/upload_spec.rb
index 4c832c87d6afcbffaaa390db292905c39beb1147..2dea2c6015f6bd22ac7bf4032f6bf6005f1c8548 100644
--- a/spec/models/upload_spec.rb
+++ b/spec/models/upload_spec.rb
@@ -54,8 +54,8 @@ describe Upload, type: :model do
         uploader: 'AvatarUploader'
       )
 
-      expect { described_class.remove_path(__FILE__) }.
-        to change { described_class.count }.from(1).to(0)
+      expect { described_class.remove_path(__FILE__) }
+        .to change { described_class.count }.from(1).to(0)
     end
   end
 
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 1a1bbd60504f1569e97d21c289088a86df9715e4..448555d2190a0b67cfe32c8abf2e10141fa8f5ea 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -451,6 +451,40 @@ describe User, models: true do
     end
   end
 
+  describe '#ensure_user_rights_and_limits' do
+    describe 'with external user' do
+      let(:user) { create(:user, external: true) }
+
+      it 'receives callback when external changes' do
+        expect(user).to receive(:ensure_user_rights_and_limits)
+
+        user.update_attributes(external: false)
+      end
+
+      it 'ensures correct rights and limits for user' do
+        stub_config_setting(default_can_create_group: true)
+
+        expect { user.update_attributes(external: false) }.to change { user.can_create_group }.to(true)
+          .and change { user.projects_limit }.to(current_application_settings.default_projects_limit)
+      end
+    end
+
+    describe 'without external user' do
+      let(:user) { create(:user, external: false) }
+
+      it 'receives callback when external changes' do
+        expect(user).to receive(:ensure_user_rights_and_limits)
+
+        user.update_attributes(external: true)
+      end
+
+      it 'ensures correct rights and limits for user' do
+        expect { user.update_attributes(external: true) }.to change { user.can_create_group }.to(false)
+          .and change { user.projects_limit }.to(0)
+      end
+    end
+  end
+
   describe 'rss token' do
     it 'ensures an rss token on read' do
       user = create(:user, rss_token: nil)
@@ -720,42 +754,49 @@ describe User, models: true do
   end
 
   describe '.search' do
-    let(:user) { create(:user) }
+    let!(:user) { create(:user, name: 'user', username: 'usern', email: 'email@gmail.com') }
+    let!(:user2) { create(:user, name: 'user name', username: 'username', email: 'someemail@gmail.com') }
 
-    it 'returns users with a matching name' do
-      expect(described_class.search(user.name)).to eq([user])
-    end
+    describe 'name matching' do
+      it 'returns users with a matching name with exact match first' do
+        expect(described_class.search(user.name)).to eq([user, user2])
+      end
 
-    it 'returns users with a partially matching name' do
-      expect(described_class.search(user.name[0..2])).to eq([user])
-    end
+      it 'returns users with a partially matching name' do
+        expect(described_class.search(user.name[0..2])).to eq([user2, user])
+      end
 
-    it 'returns users with a matching name regardless of the casing' do
-      expect(described_class.search(user.name.upcase)).to eq([user])
+      it 'returns users with a matching name regardless of the casing' do
+        expect(described_class.search(user2.name.upcase)).to eq([user2])
+      end
     end
 
-    it 'returns users with a matching Email' do
-      expect(described_class.search(user.email)).to eq([user])
-    end
+    describe 'email matching' do
+      it 'returns users with a matching Email' do
+        expect(described_class.search(user.email)).to eq([user, user2])
+      end
 
-    it 'returns users with a partially matching Email' do
-      expect(described_class.search(user.email[0..2])).to eq([user])
-    end
+      it 'returns users with a partially matching Email' do
+        expect(described_class.search(user.email[0..2])).to eq([user2, user])
+      end
 
-    it 'returns users with a matching Email regardless of the casing' do
-      expect(described_class.search(user.email.upcase)).to eq([user])
+      it 'returns users with a matching Email regardless of the casing' do
+        expect(described_class.search(user2.email.upcase)).to eq([user2])
+      end
     end
 
-    it 'returns users with a matching username' do
-      expect(described_class.search(user.username)).to eq([user])
-    end
+    describe 'username matching' do
+      it 'returns users with a matching username' do
+        expect(described_class.search(user.username)).to eq([user, user2])
+      end
 
-    it 'returns users with a partially matching username' do
-      expect(described_class.search(user.username[0..2])).to eq([user])
-    end
+      it 'returns users with a partially matching username' do
+        expect(described_class.search(user.username[0..2])).to eq([user2, user])
+      end
 
-    it 'returns users with a matching username regardless of the casing' do
-      expect(described_class.search(user.username.upcase)).to eq([user])
+      it 'returns users with a matching username regardless of the casing' do
+        expect(described_class.search(user2.username.upcase)).to eq([user2])
+      end
     end
   end
 
@@ -878,8 +919,8 @@ describe User, models: true do
 
   describe '.find_by_username!' do
     it 'raises RecordNotFound' do
-      expect { described_class.find_by_username!('JohnDoe') }.
-        to raise_error(ActiveRecord::RecordNotFound)
+      expect { described_class.find_by_username!('JohnDoe') }
+        .to raise_error(ActiveRecord::RecordNotFound)
     end
 
     it 'is case-insensitive' do
@@ -1523,8 +1564,8 @@ describe User, models: true do
     end
 
     it 'returns the projects when using an ActiveRecord relation' do
-      projects = user.
-        projects_with_reporter_access_limited_to(Project.select(:id))
+      projects = user
+        .projects_with_reporter_access_limited_to(Project.select(:id))
 
       expect(projects).to eq([project1])
     end
@@ -1699,6 +1740,20 @@ describe User, models: true do
     end
   end
 
+  describe '#full_private_access?' do
+    it 'returns false for regular user' do
+      user = build(:user)
+
+      expect(user.full_private_access?).to be_falsy
+    end
+
+    it 'returns true for admin user' do
+      user = build(:user, :admin)
+
+      expect(user.full_private_access?).to be_truthy
+    end
+  end
+
   describe '.ghost' do
     it "creates a ghost user if one isn't already present" do
       ghost = User.ghost
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index 753dc938c52dc4a8fbdb0a0dc165a51e426bf3bf..4a73552b8a6f6a230d4979fe609254132dfbc582 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -61,8 +61,8 @@ describe WikiPage, models: true do
         actual_order =
           grouped_entries.map do |page_or_dir|
             get_slugs(page_or_dir)
-          end.
-          flatten
+          end
+          .flatten
         expect(actual_order).to eq(expected_order)
       end
     end
diff --git a/spec/policies/base_policy_spec.rb b/spec/policies/base_policy_spec.rb
index 02acdcb36df2647756423081e64465a2b3d4c8d7..e1963091a7230fda32554f3b0cfda9783bff8bd6 100644
--- a/spec/policies/base_policy_spec.rb
+++ b/spec/policies/base_policy_spec.rb
@@ -3,17 +3,17 @@ require 'spec_helper'
 describe BasePolicy, models: true do
   describe '.class_for' do
     it 'detects policy class based on the subject ancestors' do
-      expect(described_class.class_for(GenericCommitStatus.new)).to eq(CommitStatusPolicy)
+      expect(DeclarativePolicy.class_for(GenericCommitStatus.new)).to eq(CommitStatusPolicy)
     end
 
     it 'detects policy class for a presented subject' do
       presentee = Ci::BuildPresenter.new(Ci::Build.new)
 
-      expect(described_class.class_for(presentee)).to eq(Ci::BuildPolicy)
+      expect(DeclarativePolicy.class_for(presentee)).to eq(Ci::BuildPolicy)
     end
 
     it 'uses GlobalPolicy when :global is given' do
-      expect(described_class.class_for(:global)).to eq(GlobalPolicy)
+      expect(DeclarativePolicy.class_for(:global)).to eq(GlobalPolicy)
     end
   end
 end
diff --git a/spec/policies/ci/build_policy_spec.rb b/spec/policies/ci/build_policy_spec.rb
index 48a139d4b83db13a5a4fee3da06dd0b8fdedab68..ace95ac70670664729cc569118ab64820cb45554 100644
--- a/spec/policies/ci/build_policy_spec.rb
+++ b/spec/policies/ci/build_policy_spec.rb
@@ -5,8 +5,8 @@ describe Ci::BuildPolicy, :models do
   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
+  let(:policy) do
+    described_class.new(user, build)
   end
 
   shared_context 'public pipelines disabled' do
@@ -21,7 +21,7 @@ describe Ci::BuildPolicy, :models do
 
       context 'when public builds are enabled' do
         it 'does not include ability to read build' do
-          expect(policies).not_to include :read_build
+          expect(policy).not_to be_allowed :read_build
         end
       end
 
@@ -29,7 +29,7 @@ describe Ci::BuildPolicy, :models do
         include_context 'public pipelines disabled'
 
         it 'does not include ability to read build' do
-          expect(policies).not_to include :read_build
+          expect(policy).not_to be_allowed :read_build
         end
       end
     end
@@ -39,7 +39,7 @@ describe Ci::BuildPolicy, :models do
 
       context 'when public builds are enabled' do
         it 'includes ability to read build' do
-          expect(policies).to include :read_build
+          expect(policy).to be_allowed :read_build
         end
       end
 
@@ -47,7 +47,7 @@ describe Ci::BuildPolicy, :models do
         include_context 'public pipelines disabled'
 
         it 'does not include ability to read build' do
-          expect(policies).not_to include :read_build
+          expect(policy).not_to be_allowed :read_build
         end
       end
     end
@@ -62,7 +62,7 @@ describe Ci::BuildPolicy, :models do
 
         context 'when public builds are enabled' do
           it 'includes ability to read build' do
-            expect(policies).to include :read_build
+            expect(policy).to be_allowed :read_build
           end
         end
 
@@ -70,7 +70,7 @@ describe Ci::BuildPolicy, :models do
           include_context 'public pipelines disabled'
 
           it 'does not include ability to read build' do
-            expect(policies).not_to include :read_build
+            expect(policy).not_to be_allowed :read_build
           end
         end
       end
@@ -82,7 +82,7 @@ describe Ci::BuildPolicy, :models do
 
         context 'when public builds are enabled' do
           it 'includes ability to read build' do
-            expect(policies).to include :read_build
+            expect(policy).to be_allowed :read_build
           end
         end
 
@@ -90,7 +90,7 @@ describe Ci::BuildPolicy, :models do
           include_context 'public pipelines disabled'
 
           it 'does not include ability to read build' do
-            expect(policies).to include :read_build
+            expect(policy).to be_allowed :read_build
           end
         end
       end
@@ -115,7 +115,7 @@ describe Ci::BuildPolicy, :models do
           end
 
           it 'does not include ability to update build' do
-            expect(policies).not_to include :update_build
+            expect(policy).to be_disallowed :update_build
           end
         end
 
@@ -125,7 +125,7 @@ describe Ci::BuildPolicy, :models do
           end
 
           it 'includes ability to update build' do
-            expect(policies).to include :update_build
+            expect(policy).to be_allowed :update_build
           end
         end
       end
@@ -135,7 +135,7 @@ describe Ci::BuildPolicy, :models do
           let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
 
           it 'includes ability to update build' do
-            expect(policies).to include :update_build
+            expect(policy).to be_allowed :update_build
           end
         end
 
@@ -143,7 +143,7 @@ describe Ci::BuildPolicy, :models do
           let(:build) { create(:ci_build, pipeline: pipeline) }
 
           it 'includes ability to update build' do
-            expect(policies).to include :update_build
+            expect(policy).to be_allowed :update_build
           end
         end
       end
diff --git a/spec/policies/ci/trigger_policy_spec.rb b/spec/policies/ci/trigger_policy_spec.rb
index 63ad5eb732267ed44d378476df2eb02caf567b6b..ed4010e723b3fbaee23d9dbcd681f54281878d35 100644
--- a/spec/policies/ci/trigger_policy_spec.rb
+++ b/spec/policies/ci/trigger_policy_spec.rb
@@ -6,36 +6,36 @@ describe Ci::TriggerPolicy, :models do
   let(:trigger) { create(:ci_trigger, project: project, owner: owner) }
 
   let(:policies) do
-    described_class.abilities(user, trigger).to_set
+    described_class.new(user, trigger)
   end
 
   shared_examples 'allows to admin and manage trigger' do
     it 'does include ability to admin trigger' do
-      expect(policies).to include :admin_trigger
+      expect(policies).to be_allowed :admin_trigger
     end
 
     it 'does include ability to manage trigger' do
-      expect(policies).to include :manage_trigger
+      expect(policies).to be_allowed :manage_trigger
     end
   end
 
   shared_examples 'allows to manage trigger' do
     it 'does not include ability to admin trigger' do
-      expect(policies).not_to include :admin_trigger
+      expect(policies).not_to be_allowed :admin_trigger
     end
 
     it 'does include ability to manage trigger' do
-      expect(policies).to include :manage_trigger
+      expect(policies).to be_allowed :manage_trigger
     end
   end
 
   shared_examples 'disallows to admin and manage trigger' do
     it 'does not include ability to admin trigger' do
-      expect(policies).not_to include :admin_trigger
+      expect(policies).not_to be_allowed :admin_trigger
     end
 
     it 'does not include ability to manage trigger' do
-      expect(policies).not_to include :manage_trigger
+      expect(policies).not_to be_allowed :manage_trigger
     end
   end
 
diff --git a/spec/policies/deploy_key_policy_spec.rb b/spec/policies/deploy_key_policy_spec.rb
index 28e10f0bfe2a005b6b634ccfe38892ceebe25040..f15f4a11f02b5fa747f7de25c2a411f797af0f3e 100644
--- a/spec/policies/deploy_key_policy_spec.rb
+++ b/spec/policies/deploy_key_policy_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe DeployKeyPolicy, models: true do
-  subject { described_class.abilities(current_user, deploy_key).to_set }
+  subject { described_class.new(current_user, deploy_key) }
 
   describe 'updating a deploy_key' do
     context 'when a regular user' do
@@ -16,7 +16,7 @@ describe DeployKeyPolicy, models: true do
           project.deploy_keys << deploy_key
         end
 
-        it { is_expected.to include(:update_deploy_key) }
+        it { is_expected.to be_allowed(:update_deploy_key) }
       end
 
       context 'tries to update private deploy key attached to other project' do
@@ -27,13 +27,13 @@ describe DeployKeyPolicy, models: true do
           other_project.deploy_keys << deploy_key
         end
 
-        it { is_expected.not_to include(:update_deploy_key) }
+        it { is_expected.to be_disallowed(:update_deploy_key) }
       end
 
       context 'tries to update public deploy key' do
         let(:deploy_key) { create(:another_deploy_key, public: true) }
 
-        it { is_expected.not_to include(:update_deploy_key) }
+        it { is_expected.to be_disallowed(:update_deploy_key) }
       end
     end
 
@@ -43,13 +43,13 @@ describe DeployKeyPolicy, models: true do
       context ' tries to update private deploy key' do
         let(:deploy_key) { create(:deploy_key, public: false) }
 
-        it { is_expected.to include(:update_deploy_key) }
+        it { is_expected.to be_allowed(:update_deploy_key) }
       end
 
       context 'when an admin user tries to update public deploy key' do
         let(:deploy_key) { create(:another_deploy_key, public: true) }
 
-        it { is_expected.to include(:update_deploy_key) }
+        it { is_expected.to be_allowed(:update_deploy_key) }
       end
     end
   end
diff --git a/spec/policies/environment_policy_spec.rb b/spec/policies/environment_policy_spec.rb
index 650432520bbdee72afe4176e4398bbf4f6214b57..035e20c745203ef0c22cb998bcc9080f4effae68 100644
--- a/spec/policies/environment_policy_spec.rb
+++ b/spec/policies/environment_policy_spec.rb
@@ -8,8 +8,8 @@ describe EnvironmentPolicy do
     create(:environment, :with_review_app, project: project)
   end
 
-  let(:policies) do
-    described_class.abilities(user, environment).to_set
+  let(:policy) do
+    described_class.new(user, environment)
   end
 
   describe '#rules' do
@@ -17,7 +17,7 @@ describe EnvironmentPolicy do
       let(:project) { create(:project, :private) }
 
       it 'does not include ability to stop environment' do
-        expect(policies).not_to include :stop_environment
+        expect(policy).to be_disallowed :stop_environment
       end
     end
 
@@ -25,7 +25,7 @@ describe EnvironmentPolicy do
       let(:project) { create(:project, :public) }
 
       it 'does not include ability to stop environment' do
-        expect(policies).not_to include :stop_environment
+        expect(policy).to be_disallowed :stop_environment
       end
     end
 
@@ -38,7 +38,7 @@ describe EnvironmentPolicy do
 
       context 'when team member has ability to stop environment' do
         it 'does includes ability to stop environment' do
-          expect(policies).to include :stop_environment
+          expect(policy).to be_allowed :stop_environment
         end
       end
 
@@ -49,7 +49,7 @@ describe EnvironmentPolicy do
         end
 
         it 'does not include ability to stop environment' do
-          expect(policies).not_to include :stop_environment
+          expect(policy).to be_disallowed :stop_environment
         end
       end
     end
diff --git a/spec/policies/global_policy_spec.rb b/spec/policies/global_policy_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..bb0fa0c0e9c76a3543bdcdf5f571ba0459398083
--- /dev/null
+++ b/spec/policies/global_policy_spec.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+describe GlobalPolicy, models: true do
+  let(:current_user) { create(:user) }
+  let(:user) { create(:user) }
+
+  subject { GlobalPolicy.new(current_user, [user]) }
+
+  describe "reading the list of users" do
+    context "for a logged in user" do
+      it { is_expected.to be_allowed(:read_users_list) }
+    end
+
+    context "for an anonymous user" do
+      let(:current_user) { nil }
+
+      context "when the public level is restricted" do
+        before do
+          stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
+        end
+
+        it { is_expected.not_to be_allowed(:read_users_list) }
+      end
+
+      context "when the public level is not restricted" do
+        before do
+          stub_application_setting(restricted_visibility_levels: [])
+        end
+
+        it { is_expected.to be_allowed(:read_users_list) }
+      end
+    end
+  end
+end
diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb
index a8331ceb5ffcb2034acb4779b0b1beb7015fe14e..06db0ea56e34b6c037deebe12fea6751a0e398a2 100644
--- a/spec/policies/group_policy_spec.rb
+++ b/spec/policies/group_policy_spec.rb
@@ -36,16 +36,24 @@ describe GroupPolicy, models: true do
     group.add_owner(owner)
   end
 
-  subject { described_class.abilities(current_user, group).to_set }
+  subject { described_class.new(current_user, group) }
+
+  def expect_allowed(*permissions)
+    permissions.each { |p| is_expected.to be_allowed(p) }
+  end
+
+  def expect_disallowed(*permissions)
+    permissions.each { |p| is_expected.not_to be_allowed(p) }
+  end
 
   context 'with no user' do
     let(:current_user) { nil }
 
     it do
-      is_expected.to include(:read_group)
-      is_expected.not_to include(*reporter_permissions)
-      is_expected.not_to include(*master_permissions)
-      is_expected.not_to include(*owner_permissions)
+      expect_allowed(:read_group)
+      expect_disallowed(*reporter_permissions)
+      expect_disallowed(*master_permissions)
+      expect_disallowed(*owner_permissions)
     end
   end
 
@@ -53,10 +61,10 @@ describe GroupPolicy, models: true do
     let(:current_user) { guest }
 
     it do
-      is_expected.to include(:read_group)
-      is_expected.not_to include(*reporter_permissions)
-      is_expected.not_to include(*master_permissions)
-      is_expected.not_to include(*owner_permissions)
+      expect_allowed(:read_group)
+      expect_disallowed(*reporter_permissions)
+      expect_disallowed(*master_permissions)
+      expect_disallowed(*owner_permissions)
     end
   end
 
@@ -64,10 +72,10 @@ describe GroupPolicy, models: true do
     let(:current_user) { reporter }
 
     it do
-      is_expected.to include(:read_group)
-      is_expected.to include(*reporter_permissions)
-      is_expected.not_to include(*master_permissions)
-      is_expected.not_to include(*owner_permissions)
+      expect_allowed(:read_group)
+      expect_allowed(*reporter_permissions)
+      expect_disallowed(*master_permissions)
+      expect_disallowed(*owner_permissions)
     end
   end
 
@@ -75,10 +83,10 @@ describe GroupPolicy, models: true do
     let(:current_user) { developer }
 
     it do
-      is_expected.to include(:read_group)
-      is_expected.to include(*reporter_permissions)
-      is_expected.not_to include(*master_permissions)
-      is_expected.not_to include(*owner_permissions)
+      expect_allowed(:read_group)
+      expect_allowed(*reporter_permissions)
+      expect_disallowed(*master_permissions)
+      expect_disallowed(*owner_permissions)
     end
   end
 
@@ -86,10 +94,10 @@ describe GroupPolicy, models: true do
     let(:current_user) { master }
 
     it do
-      is_expected.to include(:read_group)
-      is_expected.to include(*reporter_permissions)
-      is_expected.to include(*master_permissions)
-      is_expected.not_to include(*owner_permissions)
+      expect_allowed(:read_group)
+      expect_allowed(*reporter_permissions)
+      expect_allowed(*master_permissions)
+      expect_disallowed(*owner_permissions)
     end
   end
 
@@ -97,10 +105,10 @@ describe GroupPolicy, models: true do
     let(:current_user) { owner }
 
     it do
-      is_expected.to include(:read_group)
-      is_expected.to include(*reporter_permissions)
-      is_expected.to include(*master_permissions)
-      is_expected.to include(*owner_permissions)
+      expect_allowed(:read_group)
+      expect_allowed(*reporter_permissions)
+      expect_allowed(*master_permissions)
+      expect_allowed(*owner_permissions)
     end
   end
 
@@ -108,10 +116,10 @@ describe GroupPolicy, models: true do
     let(:current_user) { admin }
 
     it do
-      is_expected.to include(:read_group)
-      is_expected.to include(*reporter_permissions)
-      is_expected.to include(*master_permissions)
-      is_expected.to include(*owner_permissions)
+      expect_allowed(:read_group)
+      expect_allowed(*reporter_permissions)
+      expect_allowed(*master_permissions)
+      expect_allowed(*owner_permissions)
     end
   end
 
@@ -130,16 +138,16 @@ describe GroupPolicy, models: true do
       nested_group.add_owner(owner)
     end
 
-    subject { described_class.abilities(current_user, nested_group).to_set }
+    subject { described_class.new(current_user, nested_group) }
 
     context 'with no user' do
       let(:current_user) { nil }
 
       it do
-        is_expected.not_to include(:read_group)
-        is_expected.not_to include(*reporter_permissions)
-        is_expected.not_to include(*master_permissions)
-        is_expected.not_to include(*owner_permissions)
+        expect_disallowed(:read_group)
+        expect_disallowed(*reporter_permissions)
+        expect_disallowed(*master_permissions)
+        expect_disallowed(*owner_permissions)
       end
     end
 
@@ -147,10 +155,10 @@ describe GroupPolicy, models: true do
       let(:current_user) { guest }
 
       it do
-        is_expected.to include(:read_group)
-        is_expected.not_to include(*reporter_permissions)
-        is_expected.not_to include(*master_permissions)
-        is_expected.not_to include(*owner_permissions)
+        expect_allowed(:read_group)
+        expect_disallowed(*reporter_permissions)
+        expect_disallowed(*master_permissions)
+        expect_disallowed(*owner_permissions)
       end
     end
 
@@ -158,10 +166,10 @@ describe GroupPolicy, models: true do
       let(:current_user) { reporter }
 
       it do
-        is_expected.to include(:read_group)
-        is_expected.to include(*reporter_permissions)
-        is_expected.not_to include(*master_permissions)
-        is_expected.not_to include(*owner_permissions)
+        expect_allowed(:read_group)
+        expect_allowed(*reporter_permissions)
+        expect_disallowed(*master_permissions)
+        expect_disallowed(*owner_permissions)
       end
     end
 
@@ -169,10 +177,10 @@ describe GroupPolicy, models: true do
       let(:current_user) { developer }
 
       it do
-        is_expected.to include(:read_group)
-        is_expected.to include(*reporter_permissions)
-        is_expected.not_to include(*master_permissions)
-        is_expected.not_to include(*owner_permissions)
+        expect_allowed(:read_group)
+        expect_allowed(*reporter_permissions)
+        expect_disallowed(*master_permissions)
+        expect_disallowed(*owner_permissions)
       end
     end
 
@@ -180,10 +188,10 @@ describe GroupPolicy, models: true do
       let(:current_user) { master }
 
       it do
-        is_expected.to include(:read_group)
-        is_expected.to include(*reporter_permissions)
-        is_expected.to include(*master_permissions)
-        is_expected.not_to include(*owner_permissions)
+        expect_allowed(:read_group)
+        expect_allowed(*reporter_permissions)
+        expect_allowed(*master_permissions)
+        expect_disallowed(*owner_permissions)
       end
     end
 
@@ -191,10 +199,10 @@ describe GroupPolicy, models: true do
       let(:current_user) { owner }
 
       it do
-        is_expected.to include(:read_group)
-        is_expected.to include(*reporter_permissions)
-        is_expected.to include(*master_permissions)
-        is_expected.to include(*owner_permissions)
+        expect_allowed(:read_group)
+        expect_allowed(*reporter_permissions)
+        expect_allowed(*master_permissions)
+        expect_allowed(*owner_permissions)
       end
     end
   end
diff --git a/spec/policies/issue_policy_spec.rb b/spec/policies/issue_policy_spec.rb
index 4a07c864428c65630aab3506bc7a5bf70ee29774..c978cbd618500be4cd112be62cd4ae8905b1f8a3 100644
--- a/spec/policies/issue_policy_spec.rb
+++ b/spec/policies/issue_policy_spec.rb
@@ -9,7 +9,7 @@ describe IssuePolicy, models: true do
   let(:reporter_from_group_link) { create(:user) }
 
   def permissions(user, issue)
-    described_class.abilities(user, issue).to_set
+    described_class.new(user, issue)
   end
 
   context 'a private project' do
@@ -30,42 +30,42 @@ describe IssuePolicy, models: true do
     end
 
     it 'does not allow non-members to read issues' do
-      expect(permissions(non_member, issue)).not_to include(:read_issue, :update_issue, :admin_issue)
-      expect(permissions(non_member, issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
+      expect(permissions(non_member, issue)).to be_disallowed(:read_issue, :update_issue, :admin_issue)
+      expect(permissions(non_member, issue_no_assignee)).to be_disallowed(:read_issue, :update_issue, :admin_issue)
     end
 
     it 'allows guests to read issues' do
-      expect(permissions(guest, issue)).to include(:read_issue)
-      expect(permissions(guest, issue)).not_to include(:update_issue, :admin_issue)
+      expect(permissions(guest, issue)).to be_allowed(:read_issue)
+      expect(permissions(guest, issue)).to be_disallowed(:update_issue, :admin_issue)
 
-      expect(permissions(guest, issue_no_assignee)).to include(:read_issue)
-      expect(permissions(guest, issue_no_assignee)).not_to include(:update_issue, :admin_issue)
+      expect(permissions(guest, issue_no_assignee)).to be_allowed(:read_issue)
+      expect(permissions(guest, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue)
     end
 
     it 'allows reporters to read, update, and admin issues' do
-      expect(permissions(reporter, issue)).to include(:read_issue, :update_issue, :admin_issue)
-      expect(permissions(reporter, issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
+      expect(permissions(reporter, issue)).to be_allowed(:read_issue, :update_issue, :admin_issue)
+      expect(permissions(reporter, issue_no_assignee)).to be_allowed(:read_issue, :update_issue, :admin_issue)
     end
 
     it 'allows reporters from group links to read, update, and admin issues' do
-      expect(permissions(reporter_from_group_link, issue)).to include(:read_issue, :update_issue, :admin_issue)
-      expect(permissions(reporter_from_group_link, issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
+      expect(permissions(reporter_from_group_link, issue)).to be_allowed(:read_issue, :update_issue, :admin_issue)
+      expect(permissions(reporter_from_group_link, issue_no_assignee)).to be_allowed(:read_issue, :update_issue, :admin_issue)
     end
 
     it 'allows issue authors to read and update their issues' do
-      expect(permissions(author, issue)).to include(:read_issue, :update_issue)
-      expect(permissions(author, issue)).not_to include(:admin_issue)
+      expect(permissions(author, issue)).to be_allowed(:read_issue, :update_issue)
+      expect(permissions(author, issue)).to be_disallowed(:admin_issue)
 
-      expect(permissions(author, issue_no_assignee)).to include(:read_issue)
-      expect(permissions(author, issue_no_assignee)).not_to include(:update_issue, :admin_issue)
+      expect(permissions(author, issue_no_assignee)).to be_allowed(:read_issue)
+      expect(permissions(author, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue)
     end
 
     it 'allows issue assignees to read and update their issues' do
-      expect(permissions(assignee, issue)).to include(:read_issue, :update_issue)
-      expect(permissions(assignee, issue)).not_to include(:admin_issue)
+      expect(permissions(assignee, issue)).to be_allowed(:read_issue, :update_issue)
+      expect(permissions(assignee, issue)).to be_disallowed(:admin_issue)
 
-      expect(permissions(assignee, issue_no_assignee)).to include(:read_issue)
-      expect(permissions(assignee, issue_no_assignee)).not_to include(:update_issue, :admin_issue)
+      expect(permissions(assignee, issue_no_assignee)).to be_allowed(:read_issue)
+      expect(permissions(assignee, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue)
     end
 
     context 'with confidential issues' do
@@ -73,37 +73,37 @@ describe IssuePolicy, models: true do
       let(:confidential_issue_no_assignee) { create(:issue, :confidential, project: project) }
 
       it 'does not allow non-members to read confidential issues' do
-        expect(permissions(non_member, confidential_issue)).not_to include(:read_issue, :update_issue, :admin_issue)
-        expect(permissions(non_member, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
+        expect(permissions(non_member, confidential_issue)).to be_disallowed(:read_issue, :update_issue, :admin_issue)
+        expect(permissions(non_member, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :update_issue, :admin_issue)
       end
 
       it 'does not allow guests to read confidential issues' do
-        expect(permissions(guest, confidential_issue)).not_to include(:read_issue, :update_issue, :admin_issue)
-        expect(permissions(guest, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
+        expect(permissions(guest, confidential_issue)).to be_disallowed(:read_issue, :update_issue, :admin_issue)
+        expect(permissions(guest, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :update_issue, :admin_issue)
       end
 
       it 'allows reporters to read, update, and admin confidential issues' do
-        expect(permissions(reporter, confidential_issue)).to include(:read_issue, :update_issue, :admin_issue)
-        expect(permissions(reporter, confidential_issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
+        expect(permissions(reporter, confidential_issue)).to be_allowed(:read_issue, :update_issue, :admin_issue)
+        expect(permissions(reporter, confidential_issue_no_assignee)).to be_allowed(:read_issue, :update_issue, :admin_issue)
       end
 
       it 'allows reporters from group links to read, update, and admin confidential issues' do
-        expect(permissions(reporter_from_group_link, confidential_issue)).to include(:read_issue, :update_issue, :admin_issue)
-        expect(permissions(reporter_from_group_link, confidential_issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
+        expect(permissions(reporter_from_group_link, confidential_issue)).to be_allowed(:read_issue, :update_issue, :admin_issue)
+        expect(permissions(reporter_from_group_link, confidential_issue_no_assignee)).to be_allowed(:read_issue, :update_issue, :admin_issue)
       end
 
       it 'allows issue authors to read and update their confidential issues' do
-        expect(permissions(author, confidential_issue)).to include(:read_issue, :update_issue)
-        expect(permissions(author, confidential_issue)).not_to include(:admin_issue)
+        expect(permissions(author, confidential_issue)).to be_allowed(:read_issue, :update_issue)
+        expect(permissions(author, confidential_issue)).to be_disallowed(:admin_issue)
 
-        expect(permissions(author, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
+        expect(permissions(author, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :update_issue, :admin_issue)
       end
 
       it 'allows issue assignees to read and update their confidential issues' do
-        expect(permissions(assignee, confidential_issue)).to include(:read_issue, :update_issue)
-        expect(permissions(assignee, confidential_issue)).not_to include(:admin_issue)
+        expect(permissions(assignee, confidential_issue)).to be_allowed(:read_issue, :update_issue)
+        expect(permissions(assignee, confidential_issue)).to be_disallowed(:admin_issue)
 
-        expect(permissions(assignee, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
+        expect(permissions(assignee, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :update_issue, :admin_issue)
       end
     end
   end
@@ -123,37 +123,37 @@ describe IssuePolicy, models: true do
     end
 
     it 'allows guests to read issues' do
-      expect(permissions(guest, issue)).to include(:read_issue)
-      expect(permissions(guest, issue)).not_to include(:update_issue, :admin_issue)
+      expect(permissions(guest, issue)).to be_allowed(:read_issue)
+      expect(permissions(guest, issue)).to be_disallowed(:update_issue, :admin_issue)
 
-      expect(permissions(guest, issue_no_assignee)).to include(:read_issue)
-      expect(permissions(guest, issue_no_assignee)).not_to include(:update_issue, :admin_issue)
+      expect(permissions(guest, issue_no_assignee)).to be_allowed(:read_issue)
+      expect(permissions(guest, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue)
     end
 
     it 'allows reporters to read, update, and admin issues' do
-      expect(permissions(reporter, issue)).to include(:read_issue, :update_issue, :admin_issue)
-      expect(permissions(reporter, issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
+      expect(permissions(reporter, issue)).to be_allowed(:read_issue, :update_issue, :admin_issue)
+      expect(permissions(reporter, issue_no_assignee)).to be_allowed(:read_issue, :update_issue, :admin_issue)
     end
 
     it 'allows reporters from group links to read, update, and admin issues' do
-      expect(permissions(reporter_from_group_link, issue)).to include(:read_issue, :update_issue, :admin_issue)
-      expect(permissions(reporter_from_group_link, issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
+      expect(permissions(reporter_from_group_link, issue)).to be_allowed(:read_issue, :update_issue, :admin_issue)
+      expect(permissions(reporter_from_group_link, issue_no_assignee)).to be_allowed(:read_issue, :update_issue, :admin_issue)
     end
 
     it 'allows issue authors to read and update their issues' do
-      expect(permissions(author, issue)).to include(:read_issue, :update_issue)
-      expect(permissions(author, issue)).not_to include(:admin_issue)
+      expect(permissions(author, issue)).to be_allowed(:read_issue, :update_issue)
+      expect(permissions(author, issue)).to be_disallowed(:admin_issue)
 
-      expect(permissions(author, issue_no_assignee)).to include(:read_issue)
-      expect(permissions(author, issue_no_assignee)).not_to include(:update_issue, :admin_issue)
+      expect(permissions(author, issue_no_assignee)).to be_allowed(:read_issue)
+      expect(permissions(author, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue)
     end
 
     it 'allows issue assignees to read and update their issues' do
-      expect(permissions(assignee, issue)).to include(:read_issue, :update_issue)
-      expect(permissions(assignee, issue)).not_to include(:admin_issue)
+      expect(permissions(assignee, issue)).to be_allowed(:read_issue, :update_issue)
+      expect(permissions(assignee, issue)).to be_disallowed(:admin_issue)
 
-      expect(permissions(assignee, issue_no_assignee)).to include(:read_issue)
-      expect(permissions(assignee, issue_no_assignee)).not_to include(:update_issue, :admin_issue)
+      expect(permissions(assignee, issue_no_assignee)).to be_allowed(:read_issue)
+      expect(permissions(assignee, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue)
     end
 
     context 'with confidential issues' do
@@ -161,32 +161,32 @@ describe IssuePolicy, models: true do
       let(:confidential_issue_no_assignee) { create(:issue, :confidential, project: project) }
 
       it 'does not allow guests to read confidential issues' do
-        expect(permissions(guest, confidential_issue)).not_to include(:read_issue, :update_issue, :admin_issue)
-        expect(permissions(guest, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
+        expect(permissions(guest, confidential_issue)).to be_disallowed(:read_issue, :update_issue, :admin_issue)
+        expect(permissions(guest, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :update_issue, :admin_issue)
       end
 
       it 'allows reporters to read, update, and admin confidential issues' do
-        expect(permissions(reporter, confidential_issue)).to include(:read_issue, :update_issue, :admin_issue)
-        expect(permissions(reporter, confidential_issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
+        expect(permissions(reporter, confidential_issue)).to be_allowed(:read_issue, :update_issue, :admin_issue)
+        expect(permissions(reporter, confidential_issue_no_assignee)).to be_allowed(:read_issue, :update_issue, :admin_issue)
       end
 
       it 'allows reporter from group links to read, update, and admin confidential issues' do
-        expect(permissions(reporter_from_group_link, confidential_issue)).to include(:read_issue, :update_issue, :admin_issue)
-        expect(permissions(reporter_from_group_link, confidential_issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
+        expect(permissions(reporter_from_group_link, confidential_issue)).to be_allowed(:read_issue, :update_issue, :admin_issue)
+        expect(permissions(reporter_from_group_link, confidential_issue_no_assignee)).to be_allowed(:read_issue, :update_issue, :admin_issue)
       end
 
       it 'allows issue authors to read and update their confidential issues' do
-        expect(permissions(author, confidential_issue)).to include(:read_issue, :update_issue)
-        expect(permissions(author, confidential_issue)).not_to include(:admin_issue)
+        expect(permissions(author, confidential_issue)).to be_allowed(:read_issue, :update_issue)
+        expect(permissions(author, confidential_issue)).to be_disallowed(:admin_issue)
 
-        expect(permissions(author, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
+        expect(permissions(author, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :update_issue, :admin_issue)
       end
 
       it 'allows issue assignees to read and update their confidential issues' do
-        expect(permissions(assignee, confidential_issue)).to include(:read_issue, :update_issue)
-        expect(permissions(assignee, confidential_issue)).not_to include(:admin_issue)
+        expect(permissions(assignee, confidential_issue)).to be_allowed(:read_issue, :update_issue)
+        expect(permissions(assignee, confidential_issue)).to be_disallowed(:admin_issue)
 
-        expect(permissions(assignee, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
+        expect(permissions(assignee, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :update_issue, :admin_issue)
       end
     end
   end
diff --git a/spec/policies/personal_snippet_policy_spec.rb b/spec/policies/personal_snippet_policy_spec.rb
index 58aa1145c9ebffb813b0c2bfccf8a128abc6bf5f..4d6350fc653e809e26cea850a311bb4cde86fe5e 100644
--- a/spec/policies/personal_snippet_policy_spec.rb
+++ b/spec/policies/personal_snippet_policy_spec.rb
@@ -14,7 +14,7 @@ describe PersonalSnippetPolicy, models: true do
   end
 
   def permissions(user)
-    described_class.abilities(user, snippet).to_set
+    described_class.new(user, snippet)
   end
 
   context 'public snippet' do
@@ -24,9 +24,9 @@ describe PersonalSnippetPolicy, models: true do
       subject { permissions(nil) }
 
       it do
-        is_expected.to include(:read_personal_snippet)
-        is_expected.not_to include(:comment_personal_snippet)
-        is_expected.not_to include(*author_permissions)
+        is_expected.to be_allowed(:read_personal_snippet)
+        is_expected.to be_disallowed(:comment_personal_snippet)
+        is_expected.to be_disallowed(*author_permissions)
       end
     end
 
@@ -34,9 +34,9 @@ describe PersonalSnippetPolicy, models: true do
       subject { permissions(regular_user) }
 
       it do
-        is_expected.to include(:read_personal_snippet)
-        is_expected.to include(:comment_personal_snippet)
-        is_expected.not_to include(*author_permissions)
+        is_expected.to be_allowed(:read_personal_snippet)
+        is_expected.to be_allowed(:comment_personal_snippet)
+        is_expected.to be_disallowed(*author_permissions)
       end
     end
 
@@ -44,9 +44,9 @@ describe PersonalSnippetPolicy, models: true do
       subject { permissions(snippet.author) }
 
       it do
-        is_expected.to include(:read_personal_snippet)
-        is_expected.to include(:comment_personal_snippet)
-        is_expected.to include(*author_permissions)
+        is_expected.to be_allowed(:read_personal_snippet)
+        is_expected.to be_allowed(:comment_personal_snippet)
+        is_expected.to be_allowed(*author_permissions)
       end
     end
   end
@@ -58,9 +58,9 @@ describe PersonalSnippetPolicy, models: true do
       subject { permissions(nil) }
 
       it do
-        is_expected.not_to include(:read_personal_snippet)
-        is_expected.not_to include(:comment_personal_snippet)
-        is_expected.not_to include(*author_permissions)
+        is_expected.to be_disallowed(:read_personal_snippet)
+        is_expected.to be_disallowed(:comment_personal_snippet)
+        is_expected.to be_disallowed(*author_permissions)
       end
     end
 
@@ -68,9 +68,9 @@ describe PersonalSnippetPolicy, models: true do
       subject { permissions(regular_user) }
 
       it do
-        is_expected.to include(:read_personal_snippet)
-        is_expected.to include(:comment_personal_snippet)
-        is_expected.not_to include(*author_permissions)
+        is_expected.to be_allowed(:read_personal_snippet)
+        is_expected.to be_allowed(:comment_personal_snippet)
+        is_expected.to be_disallowed(*author_permissions)
       end
     end
 
@@ -78,9 +78,9 @@ describe PersonalSnippetPolicy, models: true do
       subject { permissions(external_user) }
 
       it do
-        is_expected.not_to include(:read_personal_snippet)
-        is_expected.not_to include(:comment_personal_snippet)
-        is_expected.not_to include(*author_permissions)
+        is_expected.to be_disallowed(:read_personal_snippet)
+        is_expected.to be_disallowed(:comment_personal_snippet)
+        is_expected.to be_disallowed(*author_permissions)
       end
     end
 
@@ -88,9 +88,9 @@ describe PersonalSnippetPolicy, models: true do
       subject { permissions(snippet.author) }
 
       it do
-        is_expected.to include(:read_personal_snippet)
-        is_expected.to include(:comment_personal_snippet)
-        is_expected.to include(*author_permissions)
+        is_expected.to be_allowed(:read_personal_snippet)
+        is_expected.to be_allowed(:comment_personal_snippet)
+        is_expected.to be_allowed(*author_permissions)
       end
     end
   end
@@ -102,9 +102,9 @@ describe PersonalSnippetPolicy, models: true do
       subject { permissions(nil) }
 
       it do
-        is_expected.not_to include(:read_personal_snippet)
-        is_expected.not_to include(:comment_personal_snippet)
-        is_expected.not_to include(*author_permissions)
+        is_expected.to be_disallowed(:read_personal_snippet)
+        is_expected.to be_disallowed(:comment_personal_snippet)
+        is_expected.to be_disallowed(*author_permissions)
       end
     end
 
@@ -112,9 +112,9 @@ describe PersonalSnippetPolicy, models: true do
       subject { permissions(regular_user) }
 
       it do
-        is_expected.not_to include(:read_personal_snippet)
-        is_expected.not_to include(:comment_personal_snippet)
-        is_expected.not_to include(*author_permissions)
+        is_expected.to be_disallowed(:read_personal_snippet)
+        is_expected.to be_disallowed(:comment_personal_snippet)
+        is_expected.to be_disallowed(*author_permissions)
       end
     end
 
@@ -122,9 +122,9 @@ describe PersonalSnippetPolicy, models: true do
       subject { permissions(external_user) }
 
       it do
-        is_expected.not_to include(:read_personal_snippet)
-        is_expected.not_to include(:comment_personal_snippet)
-        is_expected.not_to include(*author_permissions)
+        is_expected.to be_disallowed(:read_personal_snippet)
+        is_expected.to be_disallowed(:comment_personal_snippet)
+        is_expected.to be_disallowed(*author_permissions)
       end
     end
 
@@ -132,9 +132,9 @@ describe PersonalSnippetPolicy, models: true do
       subject { permissions(snippet.author) }
 
       it do
-        is_expected.to include(:read_personal_snippet)
-        is_expected.to include(:comment_personal_snippet)
-        is_expected.to include(*author_permissions)
+        is_expected.to be_allowed(:read_personal_snippet)
+        is_expected.to be_allowed(:comment_personal_snippet)
+        is_expected.to be_allowed(*author_permissions)
       end
     end
   end
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index 848fd547e104860f50e04200fc47c55f51490877..ca435dd021828a6fb70b62a26ad277dd83be0fbd 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -73,37 +73,45 @@ describe ProjectPolicy, models: true do
     project.team << [reporter, :reporter]
   end
 
+  def expect_allowed(*permissions)
+    permissions.each { |p| is_expected.to be_allowed(p) }
+  end
+
+  def expect_disallowed(*permissions)
+    permissions.each { |p| is_expected.not_to be_allowed(p) }
+  end
+
   it 'does not include the read_issue permission when the issue author is not a member of the private project' do
     project = create(:empty_project, :private)
     issue   = create(:issue, project: project)
     user    = issue.author
 
-    expect(project.team.member?(issue.author)).to eq(false)
+    expect(project.team.member?(issue.author)).to be false
 
-    expect(BasePolicy.class_for(project).abilities(user, project).can_set).
-      not_to include(:read_issue)
-
-    expect(Ability.allowed?(user, :read_issue, project)).to be_falsy
+    expect(Ability).not_to be_allowed(user, :read_issue, project)
   end
 
-  it 'does not include the wiki permissions when the feature is disabled' do
-    project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED)
-    wiki_permissions = [:read_wiki, :create_wiki, :update_wiki, :admin_wiki, :download_wiki_code]
+  context 'when the feature is disabled' do
+    subject { described_class.new(owner, project) }
 
-    permissions = described_class.abilities(owner, project).to_set
+    before do
+      project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED)
+    end
 
-    expect(permissions).not_to include(*wiki_permissions)
+    it 'does not include the wiki permissions' do
+      expect_disallowed :read_wiki, :create_wiki, :update_wiki, :admin_wiki, :download_wiki_code
+    end
   end
 
   context 'abilities for non-public projects' do
     let(:project) { create(:empty_project, namespace: owner.namespace) }
 
-    subject { described_class.abilities(current_user, project).to_set }
+    subject { described_class.new(current_user, project) }
 
     context 'with no user' do
       let(:current_user) { nil }
 
-      it { is_expected.to be_empty }
+      it { is_expected.to be_banned }
     end
 
     context 'guests' do
@@ -114,18 +122,18 @@ describe ProjectPolicy, models: true do
       end
 
       it do
-        is_expected.to include(*guest_permissions)
-        is_expected.not_to include(*reporter_public_build_permissions)
-        is_expected.not_to include(*team_member_reporter_permissions)
-        is_expected.not_to include(*developer_permissions)
-        is_expected.not_to include(*master_permissions)
-        is_expected.not_to include(*owner_permissions)
+        expect_allowed(*guest_permissions)
+        expect_disallowed(*reporter_public_build_permissions)
+        expect_disallowed(*team_member_reporter_permissions)
+        expect_disallowed(*developer_permissions)
+        expect_disallowed(*master_permissions)
+        expect_disallowed(*owner_permissions)
       end
 
       context 'public builds enabled' do
         it do
-          is_expected.to include(*guest_permissions)
-          is_expected.to include(:read_build, :read_pipeline)
+          expect_allowed(*guest_permissions)
+          expect_allowed(:read_build, :read_pipeline)
         end
       end
 
@@ -135,8 +143,8 @@ describe ProjectPolicy, models: true do
         end
 
         it do
-          is_expected.to include(*guest_permissions)
-          is_expected.not_to include(:read_build, :read_pipeline)
+          expect_allowed(*guest_permissions)
+          expect_disallowed(:read_build, :read_pipeline)
         end
       end
 
@@ -147,8 +155,8 @@ describe ProjectPolicy, models: true do
         end
 
         it do
-          is_expected.not_to include(:read_build)
-          is_expected.to include(:read_pipeline)
+          expect_disallowed(:read_build)
+          expect_allowed(:read_pipeline)
         end
       end
     end
@@ -157,12 +165,13 @@ describe ProjectPolicy, models: true do
       let(:current_user) { reporter }
 
       it do
-        is_expected.to include(*guest_permissions)
-        is_expected.to include(*reporter_permissions)
-        is_expected.to include(*team_member_reporter_permissions)
-        is_expected.not_to include(*developer_permissions)
-        is_expected.not_to include(*master_permissions)
-        is_expected.not_to include(*owner_permissions)
+        expect_allowed(*guest_permissions)
+        expect_allowed(*reporter_permissions)
+        expect_allowed(*reporter_permissions)
+        expect_allowed(*team_member_reporter_permissions)
+        expect_disallowed(*developer_permissions)
+        expect_disallowed(*master_permissions)
+        expect_disallowed(*owner_permissions)
       end
     end
 
@@ -170,12 +179,12 @@ describe ProjectPolicy, models: true do
       let(:current_user) { dev }
 
       it do
-        is_expected.to include(*guest_permissions)
-        is_expected.to include(*reporter_permissions)
-        is_expected.to include(*team_member_reporter_permissions)
-        is_expected.to include(*developer_permissions)
-        is_expected.not_to include(*master_permissions)
-        is_expected.not_to include(*owner_permissions)
+        expect_allowed(*guest_permissions)
+        expect_allowed(*reporter_permissions)
+        expect_allowed(*team_member_reporter_permissions)
+        expect_allowed(*developer_permissions)
+        expect_disallowed(*master_permissions)
+        expect_disallowed(*owner_permissions)
       end
     end
 
@@ -183,12 +192,12 @@ describe ProjectPolicy, models: true do
       let(:current_user) { master }
 
       it do
-        is_expected.to include(*guest_permissions)
-        is_expected.to include(*reporter_permissions)
-        is_expected.to include(*team_member_reporter_permissions)
-        is_expected.to include(*developer_permissions)
-        is_expected.to include(*master_permissions)
-        is_expected.not_to include(*owner_permissions)
+        expect_allowed(*guest_permissions)
+        expect_allowed(*reporter_permissions)
+        expect_allowed(*team_member_reporter_permissions)
+        expect_allowed(*developer_permissions)
+        expect_allowed(*master_permissions)
+        expect_disallowed(*owner_permissions)
       end
     end
 
@@ -196,12 +205,12 @@ describe ProjectPolicy, models: true do
       let(:current_user) { owner }
 
       it do
-        is_expected.to include(*guest_permissions)
-        is_expected.to include(*reporter_permissions)
-        is_expected.to include(*team_member_reporter_permissions)
-        is_expected.to include(*developer_permissions)
-        is_expected.to include(*master_permissions)
-        is_expected.to include(*owner_permissions)
+        expect_allowed(*guest_permissions)
+        expect_allowed(*reporter_permissions)
+        expect_allowed(*team_member_reporter_permissions)
+        expect_allowed(*developer_permissions)
+        expect_allowed(*master_permissions)
+        expect_allowed(*owner_permissions)
       end
     end
 
@@ -209,12 +218,12 @@ describe ProjectPolicy, models: true do
       let(:current_user) { admin }
 
       it do
-        is_expected.to include(*guest_permissions)
-        is_expected.to include(*reporter_permissions)
-        is_expected.not_to include(*team_member_reporter_permissions)
-        is_expected.to include(*developer_permissions)
-        is_expected.to include(*master_permissions)
-        is_expected.to include(*owner_permissions)
+        expect_allowed(*guest_permissions)
+        expect_allowed(*reporter_permissions)
+        expect_disallowed(*team_member_reporter_permissions)
+        expect_allowed(*developer_permissions)
+        expect_allowed(*master_permissions)
+        expect_allowed(*owner_permissions)
       end
     end
   end
diff --git a/spec/policies/project_snippet_policy_spec.rb b/spec/policies/project_snippet_policy_spec.rb
index d2b2528c57a28b3a17303d279fa02f1c1f010c1d..2799f03fb9bf8aefa9e1652d912165ecda549f2c 100644
--- a/spec/policies/project_snippet_policy_spec.rb
+++ b/spec/policies/project_snippet_policy_spec.rb
@@ -15,7 +15,15 @@ describe ProjectSnippetPolicy, models: true do
   def abilities(user, snippet_visibility)
     snippet = create(:project_snippet, snippet_visibility, project: project)
 
-    described_class.abilities(user, snippet).to_set
+    described_class.new(user, snippet)
+  end
+
+  def expect_allowed(*permissions)
+    permissions.each { |p| is_expected.to be_allowed(p) }
+  end
+
+  def expect_disallowed(*permissions)
+    permissions.each { |p| is_expected.not_to be_allowed(p) }
   end
 
   context 'public snippet' do
@@ -23,8 +31,8 @@ describe ProjectSnippetPolicy, models: true do
       subject { abilities(nil, :public) }
 
       it do
-        is_expected.to include(:read_project_snippet)
-        is_expected.not_to include(*author_permissions)
+        expect_allowed(:read_project_snippet)
+        expect_disallowed(*author_permissions)
       end
     end
 
@@ -32,8 +40,8 @@ describe ProjectSnippetPolicy, models: true do
       subject { abilities(regular_user, :public) }
 
       it do
-        is_expected.to include(:read_project_snippet)
-        is_expected.not_to include(*author_permissions)
+        expect_allowed(:read_project_snippet)
+        expect_disallowed(*author_permissions)
       end
     end
 
@@ -41,8 +49,8 @@ describe ProjectSnippetPolicy, models: true do
       subject { abilities(external_user, :public) }
 
       it do
-        is_expected.to include(:read_project_snippet)
-        is_expected.not_to include(*author_permissions)
+        expect_allowed(:read_project_snippet)
+        expect_disallowed(*author_permissions)
       end
     end
   end
@@ -52,8 +60,8 @@ describe ProjectSnippetPolicy, models: true do
       subject { abilities(nil, :internal) }
 
       it do
-        is_expected.not_to include(:read_project_snippet)
-        is_expected.not_to include(*author_permissions)
+        expect_disallowed(:read_project_snippet)
+        expect_disallowed(*author_permissions)
       end
     end
 
@@ -61,8 +69,8 @@ describe ProjectSnippetPolicy, models: true do
       subject { abilities(regular_user, :internal) }
 
       it do
-        is_expected.to include(:read_project_snippet)
-        is_expected.not_to include(*author_permissions)
+        expect_allowed(:read_project_snippet)
+        expect_disallowed(*author_permissions)
       end
     end
 
@@ -70,8 +78,8 @@ describe ProjectSnippetPolicy, models: true do
       subject { abilities(external_user, :internal) }
 
       it do
-        is_expected.not_to include(:read_project_snippet)
-        is_expected.not_to include(*author_permissions)
+        expect_disallowed(:read_project_snippet)
+        expect_disallowed(*author_permissions)
       end
     end
 
@@ -83,8 +91,8 @@ describe ProjectSnippetPolicy, models: true do
       end
 
       it do
-        is_expected.to include(:read_project_snippet)
-        is_expected.not_to include(*author_permissions)
+        expect_allowed(:read_project_snippet)
+        expect_disallowed(*author_permissions)
       end
     end
   end
@@ -94,8 +102,8 @@ describe ProjectSnippetPolicy, models: true do
       subject { abilities(nil, :private) }
 
       it do
-        is_expected.not_to include(:read_project_snippet)
-        is_expected.not_to include(*author_permissions)
+        expect_disallowed(:read_project_snippet)
+        expect_disallowed(*author_permissions)
       end
     end
 
@@ -103,19 +111,19 @@ describe ProjectSnippetPolicy, models: true do
       subject { abilities(regular_user, :private) }
 
       it do
-        is_expected.not_to include(:read_project_snippet)
-        is_expected.not_to include(*author_permissions)
+        expect_disallowed(:read_project_snippet)
+        expect_disallowed(*author_permissions)
       end
     end
 
     context 'snippet author' do
       let(:snippet) { create(:project_snippet, :private, author: regular_user, project: project) }
 
-      subject { described_class.abilities(regular_user, snippet).to_set }
+      subject { described_class.new(regular_user, snippet) }
 
       it do
-        is_expected.to include(:read_project_snippet)
-        is_expected.to include(*author_permissions)
+        expect_allowed(:read_project_snippet)
+        expect_allowed(*author_permissions)
       end
     end
 
@@ -127,8 +135,8 @@ describe ProjectSnippetPolicy, models: true do
       end
 
       it do
-        is_expected.to include(:read_project_snippet)
-        is_expected.not_to include(*author_permissions)
+        expect_allowed(:read_project_snippet)
+        expect_disallowed(*author_permissions)
       end
     end
 
@@ -140,8 +148,8 @@ describe ProjectSnippetPolicy, models: true do
       end
 
       it do
-        is_expected.to include(:read_project_snippet)
-        is_expected.not_to include(*author_permissions)
+        expect_allowed(:read_project_snippet)
+        expect_disallowed(*author_permissions)
       end
     end
 
@@ -149,8 +157,8 @@ describe ProjectSnippetPolicy, models: true do
       subject { abilities(create(:admin), :private) }
 
       it do
-        is_expected.to include(:read_project_snippet)
-        is_expected.to include(*author_permissions)
+        expect_allowed(:read_project_snippet)
+        expect_allowed(*author_permissions)
       end
     end
   end
diff --git a/spec/policies/user_policy_spec.rb b/spec/policies/user_policy_spec.rb
index d5761390d39fa122c1572aff07d959309f260801..0251d5dcf1c85c0b772fb249df170022a7620395 100644
--- a/spec/policies/user_policy_spec.rb
+++ b/spec/policies/user_policy_spec.rb
@@ -4,34 +4,34 @@ describe UserPolicy, models: true do
   let(:current_user) { create(:user) }
   let(:user) { create(:user) }
 
-  subject { described_class.abilities(current_user, user).to_set }
+  subject { UserPolicy.new(current_user, user) }
 
   describe "reading a user's information" do
-    it { is_expected.to include(:read_user) }
+    it { is_expected.to be_allowed(:read_user) }
   end
 
   describe "destroying a user" do
     context "when a regular user tries to destroy another regular user" do
-      it { is_expected.not_to include(:destroy_user) }
+      it { is_expected.not_to be_allowed(:destroy_user) }
     end
 
     context "when a regular user tries to destroy themselves" do
       let(:current_user) { user }
 
-      it { is_expected.to include(:destroy_user) }
+      it { is_expected.to be_allowed(:destroy_user) }
     end
 
     context "when an admin user tries to destroy a regular user" do
       let(:current_user) { create(:user, :admin) }
 
-      it { is_expected.to include(:destroy_user) }
+      it { is_expected.to be_allowed(:destroy_user) }
     end
 
     context "when an admin user tries to destroy a ghost user" do
       let(:current_user) { create(:user, :admin) }
       let(:user) { create(:user, :ghost) }
 
-      it { is_expected.not_to include(:destroy_user) }
+      it { is_expected.not_to be_allowed(:destroy_user) }
     end
   end
 end
diff --git a/spec/presenters/ci/build_presenter_spec.rb b/spec/presenters/ci/build_presenter_spec.rb
index 2190ab0e82e297cc8f58f9610a21f2392e754d18..518e97d17a1c461e3305763d577e1d07b6675302 100644
--- a/spec/presenters/ci/build_presenter_spec.rb
+++ b/spec/presenters/ci/build_presenter_spec.rb
@@ -47,8 +47,8 @@ describe Ci::BuildPresenter do
     context 'when build is erased' do
       before do
         expect(presenter).to receive(:erased_by_user?).and_return(true)
-        expect(build).to receive(:erased_by).
-          and_return(double(:user, name: 'John Doe'))
+        expect(build).to receive(:erased_by)
+          .and_return(double(:user, name: 'John Doe'))
       end
 
       it 'returns the name of the eraser' do
diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb
index b8ca73c321c4fc7592bed3d9dc2437775e506e60..8b62aa268d91a2ced279b44c11ccfc1bcfa18815 100644
--- a/spec/requests/api/commit_statuses_spec.rb
+++ b/spec/requests/api/commit_statuses_spec.rb
@@ -164,25 +164,40 @@ describe API::CommitStatuses do
 
       context 'with all optional parameters' do
         context 'when creating a commit status' do
-          it 'creates commit status' do
+          subject do
             post api(post_url, developer), {
               state: 'success',
               context: 'coverage',
-              ref: 'develop',
+              ref: 'master',
               description: 'test',
               coverage: 80.0,
               target_url: 'http://gitlab.com/status'
             }
+          end
+
+          it 'creates commit status' do
+            subject
 
             expect(response).to have_http_status(201)
             expect(json_response['sha']).to eq(commit.id)
             expect(json_response['status']).to eq('success')
             expect(json_response['name']).to eq('coverage')
-            expect(json_response['ref']).to eq('develop')
+            expect(json_response['ref']).to eq('master')
             expect(json_response['coverage']).to eq(80.0)
             expect(json_response['description']).to eq('test')
             expect(json_response['target_url']).to eq('http://gitlab.com/status')
           end
+
+          context 'when merge request exists for given branch' do
+            let!(:merge_request) { create(:merge_request, source_project: project, source_branch: 'master', target_branch: 'develop') }
+
+            it 'sets head pipeline' do
+              subject
+
+              expect(response).to have_http_status(201)
+              expect(merge_request.reload.head_pipeline).not_to be_nil
+            end
+          end
         end
 
         context 'when updatig a commit status' do
@@ -190,7 +205,7 @@ describe API::CommitStatuses do
             post api(post_url, developer), {
               state: 'running',
               context: 'coverage',
-              ref: 'develop',
+              ref: 'master',
               description: 'coverage test',
               coverage: 0.0,
               target_url: 'http://gitlab.com/status'
@@ -199,7 +214,7 @@ describe API::CommitStatuses do
             post api(post_url, developer), {
               state: 'success',
               name: 'coverage',
-              ref: 'develop',
+              ref: 'master',
               description: 'new description',
               coverage: 90.0
             }
@@ -210,7 +225,7 @@ describe API::CommitStatuses do
             expect(json_response['sha']).to eq(commit.id)
             expect(json_response['status']).to eq('success')
             expect(json_response['name']).to eq('coverage')
-            expect(json_response['ref']).to eq('develop')
+            expect(json_response['ref']).to eq('master')
             expect(json_response['coverage']).to eq(90.0)
             expect(json_response['description']).to eq('new description')
             expect(json_response['target_url']).to eq('http://gitlab.com/status')
@@ -222,6 +237,28 @@ describe API::CommitStatuses do
         end
       end
 
+      context 'when retrying a commit status' do
+        before do
+          post api(post_url, developer),
+            { state: 'failed', name: 'test', ref: 'master' }
+
+          post api(post_url, developer),
+            { state: 'success', name: 'test', ref: 'master' }
+        end
+
+        it 'correctly posts a new commit status' do
+          expect(response).to have_http_status(201)
+          expect(json_response['sha']).to eq(commit.id)
+          expect(json_response['status']).to eq('success')
+        end
+
+        it 'retries a commit status' do
+          expect(CommitStatus.count).to eq 2
+          expect(CommitStatus.first).to be_retried
+          expect(CommitStatus.last.pipeline).to be_success
+        end
+      end
+
       context 'when status is invalid' do
         before do
           post api(post_url, developer), state: 'invalid'
diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb
index 9c260f88f56f70a80609d836ecfe88532b7b7c73..32439981b609ad5c7e0f47207f4bee8c89165838 100644
--- a/spec/requests/api/deploy_keys_spec.rb
+++ b/spec/requests/api/deploy_keys_spec.rb
@@ -160,6 +160,16 @@ describe API::DeployKeys do
       expect(json_response['title']).to eq('new title')
       expect(json_response['can_push']).to eq(true)
     end
+
+    it 'updates a private ssh key from projects user has access with correct attributes' do
+      create(:deploy_keys_project, project: project2, deploy_key: private_deploy_key)
+
+      put api("/projects/#{project.id}/deploy_keys/#{private_deploy_key.id}", admin), { title: 'new title', can_push: true }
+
+      expect(json_response['id']).to eq(private_deploy_key.id)
+      expect(json_response['title']).to eq('new title')
+      expect(json_response['can_push']).to eq(true)
+    end
   end
 
   describe 'DELETE /projects/:id/deploy_keys/:key_id' do
diff --git a/spec/requests/api/features_spec.rb b/spec/requests/api/features_spec.rb
index f169e6661d19cd156f1ad390f5c13dcf6c77fd81..1d8aaeea8f2279f58a9b2ba4025207925609cce1 100644
--- a/spec/requests/api/features_spec.rb
+++ b/spec/requests/api/features_spec.rb
@@ -4,6 +4,13 @@ describe API::Features do
   let(:user)  { create(:user) }
   let(:admin) { create(:admin) }
 
+  before do
+    Flipper.unregister_groups
+    Flipper.register(:perf_team) do |actor|
+      actor.respond_to?(:admin) && actor.admin?
+    end
+  end
+
   describe 'GET /features' do
     let(:expected_features) do
       [
@@ -16,6 +23,14 @@ describe API::Features do
           'name' => 'feature_2',
           'state' => 'off',
           'gates' => [{ 'key' => 'boolean', 'value' => false }]
+        },
+        {
+          'name' => 'feature_3',
+          'state' => 'conditional',
+          'gates' => [
+            { 'key' => 'boolean', 'value' => false },
+            { 'key' => 'groups', 'value' => ['perf_team'] }
+          ]
         }
       ]
     end
@@ -23,6 +38,7 @@ describe API::Features do
     before do
       Feature.get('feature_1').enable
       Feature.get('feature_2').disable
+      Feature.get('feature_3').enable Feature.group(:perf_team)
     end
 
     it 'returns a 401 for anonymous users' do
@@ -47,30 +63,70 @@ describe API::Features do
 
   describe 'POST /feature' do
     let(:feature_name) { 'my_feature' }
-    it 'returns a 401 for anonymous users' do
-      post api("/features/#{feature_name}")
 
-      expect(response).to have_http_status(401)
-    end
+    context 'when the feature does not exist' do
+      it 'returns a 401 for anonymous users' do
+        post api("/features/#{feature_name}")
 
-    it 'returns a 403 for users' do
-      post api("/features/#{feature_name}", user)
+        expect(response).to have_http_status(401)
+      end
 
-      expect(response).to have_http_status(403)
-    end
+      it 'returns a 403 for users' do
+        post api("/features/#{feature_name}", user)
 
-    it 'creates an enabled feature if passed true' do
-      post api("/features/#{feature_name}", admin), value: 'true'
+        expect(response).to have_http_status(403)
+      end
 
-      expect(response).to have_http_status(201)
-      expect(Feature.get(feature_name)).to be_enabled
-    end
+      context 'when passed value=true' do
+        it 'creates an enabled feature' do
+          post api("/features/#{feature_name}", admin), value: 'true'
 
-    it 'creates a feature with the given percentage if passed an integer' do
-      post api("/features/#{feature_name}", admin), value: '50'
+          expect(response).to have_http_status(201)
+          expect(json_response).to eq(
+            'name' => 'my_feature',
+            'state' => 'on',
+            'gates' => [{ 'key' => 'boolean', 'value' => true }])
+        end
+
+        it 'creates an enabled feature for the given Flipper group when passed feature_group=perf_team' do
+          post api("/features/#{feature_name}", admin), value: 'true', feature_group: 'perf_team'
+
+          expect(response).to have_http_status(201)
+          expect(json_response).to eq(
+            'name' => 'my_feature',
+            'state' => 'conditional',
+            'gates' => [
+              { 'key' => 'boolean', 'value' => false },
+              { 'key' => 'groups', 'value' => ['perf_team'] }
+            ])
+        end
+
+        it 'creates an enabled feature for the given user when passed user=username' do
+          post api("/features/#{feature_name}", admin), value: 'true', user: user.username
 
-      expect(response).to have_http_status(201)
-      expect(Feature.get(feature_name).percentage_of_time_value).to be(50)
+          expect(response).to have_http_status(201)
+          expect(json_response).to eq(
+            'name' => 'my_feature',
+            'state' => 'conditional',
+            'gates' => [
+              { 'key' => 'boolean', 'value' => false },
+              { 'key' => 'actors', 'value' => ["User:#{user.id}"] }
+            ])
+        end
+      end
+
+      it 'creates a feature with the given percentage if passed an integer' do
+        post api("/features/#{feature_name}", admin), value: '50'
+
+        expect(response).to have_http_status(201)
+        expect(json_response).to eq(
+          'name' => 'my_feature',
+          'state' => 'conditional',
+          'gates' => [
+            { 'key' => 'boolean', 'value' => false },
+            { 'key' => 'percentage_of_time', 'value' => 50 }
+          ])
+      end
     end
 
     context 'when the feature exists' do
@@ -80,11 +136,83 @@ describe API::Features do
         feature.disable # This also persists the feature on the DB
       end
 
-      it 'enables the feature if passed true' do
-        post api("/features/#{feature_name}", admin), value: 'true'
+      context 'when passed value=true' do
+        it 'enables the feature' do
+          post api("/features/#{feature_name}", admin), value: 'true'
 
-        expect(response).to have_http_status(201)
-        expect(feature).to be_enabled
+          expect(response).to have_http_status(201)
+          expect(json_response).to eq(
+            'name' => 'my_feature',
+            'state' => 'on',
+            'gates' => [{ 'key' => 'boolean', 'value' => true }])
+        end
+
+        it 'enables the feature for the given Flipper group when passed feature_group=perf_team' do
+          post api("/features/#{feature_name}", admin), value: 'true', feature_group: 'perf_team'
+
+          expect(response).to have_http_status(201)
+          expect(json_response).to eq(
+            'name' => 'my_feature',
+            'state' => 'conditional',
+            'gates' => [
+              { 'key' => 'boolean', 'value' => false },
+              { 'key' => 'groups', 'value' => ['perf_team'] }
+            ])
+        end
+
+        it 'enables the feature for the given user when passed user=username' do
+          post api("/features/#{feature_name}", admin), value: 'true', user: user.username
+
+          expect(response).to have_http_status(201)
+          expect(json_response).to eq(
+            'name' => 'my_feature',
+            'state' => 'conditional',
+            'gates' => [
+              { 'key' => 'boolean', 'value' => false },
+              { 'key' => 'actors', 'value' => ["User:#{user.id}"] }
+            ])
+        end
+      end
+
+      context 'when feature is enabled and value=false is passed' do
+        it 'disables the feature' do
+          feature.enable
+          expect(feature).to be_enabled
+
+          post api("/features/#{feature_name}", admin), value: 'false'
+
+          expect(response).to have_http_status(201)
+          expect(json_response).to eq(
+            'name' => 'my_feature',
+            'state' => 'off',
+            'gates' => [{ 'key' => 'boolean', 'value' => false }])
+        end
+
+        it 'disables the feature for the given Flipper group when passed feature_group=perf_team' do
+          feature.enable(Feature.group(:perf_team))
+          expect(Feature.get(feature_name).enabled?(admin)).to be_truthy
+
+          post api("/features/#{feature_name}", admin), value: 'false', feature_group: 'perf_team'
+
+          expect(response).to have_http_status(201)
+          expect(json_response).to eq(
+            'name' => 'my_feature',
+            'state' => 'off',
+            'gates' => [{ 'key' => 'boolean', 'value' => false }])
+        end
+
+        it 'disables the feature for the given user when passed user=username' do
+          feature.enable(user)
+          expect(Feature.get(feature_name).enabled?(user)).to be_truthy
+
+          post api("/features/#{feature_name}", admin), value: 'false', user: user.username
+
+          expect(response).to have_http_status(201)
+          expect(json_response).to eq(
+            'name' => 'my_feature',
+            'state' => 'off',
+            'gates' => [{ 'key' => 'boolean', 'value' => false }])
+        end
       end
 
       context 'with a pre-existing percentage value' do
@@ -96,7 +224,13 @@ describe API::Features do
           post api("/features/#{feature_name}", admin), value: '30'
 
           expect(response).to have_http_status(201)
-          expect(Feature.get(feature_name).percentage_of_time_value).to be(30)
+          expect(json_response).to eq(
+            'name' => 'my_feature',
+            'state' => 'conditional',
+            'gates' => [
+              { 'key' => 'boolean', 'value' => false },
+              { 'key' => 'percentage_of_time', 'value' => 30 }
+            ])
         end
       end
     end
diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb
index c5ec8be4f219f39ef555a6d3031a90b1fff5892a..9e268adf95033c5c29cbcf6ee930f55da729659d 100644
--- a/spec/requests/api/files_spec.rb
+++ b/spec/requests/api/files_spec.rb
@@ -205,8 +205,8 @@ describe API::Files do
     end
 
     it "returns a 400 if editor fails to create file" do
-      allow_any_instance_of(Repository).to receive(:create_file).
-        and_raise(Repository::CommitError, 'Cannot create file')
+      allow_any_instance_of(Repository).to receive(:create_file)
+        .and_raise(Repository::CommitError, 'Cannot create file')
 
       post api(route("any%2Etxt"), user), valid_params
 
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index bb53796cbd7c63214c82bae2e829b4c0217ac985..656f098aea8905c3a81d64eb74cc8c81db54a631 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -513,8 +513,8 @@ describe API::Groups do
     let(:project_path) { project.full_path.gsub('/', '%2F') }
 
     before(:each) do
-      allow_any_instance_of(Projects::TransferService).
-        to receive(:execute).and_return(true)
+      allow_any_instance_of(Projects::TransferService)
+        .to receive(:execute).and_return(true)
     end
 
     context "when authenticated as user" do
diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb
index 191c60aba315317928675741abeb581f4d4ed2cf..25ec44fa036918bf405e19f8bb5f019cd6826da5 100644
--- a/spec/requests/api/helpers_spec.rb
+++ b/spec/requests/api/helpers_spec.rb
@@ -14,6 +14,10 @@ describe API::Helpers do
   let(:request) { Rack::Request.new(env) }
   let(:header) { }
 
+  before do
+    allow_any_instance_of(self.class).to receive(:options).and_return({})
+  end
+
   def set_env(user_or_token, identifier)
     clear_env
     clear_param
@@ -167,7 +171,6 @@ describe API::Helpers do
       it "returns nil for a token without the appropriate scope" do
         personal_access_token = create(:personal_access_token, user: user, scopes: ['read_user'])
         env[API::APIGuard::PRIVATE_TOKEN_HEADER] = personal_access_token.token
-        allow_access_with_scope('write_user')
 
         expect(current_user).to be_nil
       end
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index 86e15d896df7850ce98adedc0158a954c766b09a..6deaea956e03a1fd73ce230f871e93b7db9fb8a6 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -321,8 +321,6 @@ describe API::Internal do
     end
 
     context "archived project" do
-      let(:personal_project) { create(:empty_project, namespace: user.namespace) }
-
       before do
         project.team << [user, :developer]
         project.archive!
@@ -445,6 +443,42 @@ describe API::Internal do
         expect(json_response['status']).to be_truthy
       end
     end
+
+    context 'the project path was changed' do
+      let!(:old_path_to_repo) { project.repository.path_to_repo }
+      let!(:old_full_path) { project.full_path }
+      let(:project_moved_message) do
+        <<-MSG.strip_heredoc
+          Project '#{old_full_path}' was moved to '#{project.full_path}'.
+
+          Please update your Git remote and try again:
+
+            git remote set-url origin #{project.ssh_url_to_repo}
+        MSG
+      end
+
+      before do
+        project.team << [user, :developer]
+        project.path = 'new_path'
+        project.save!
+      end
+
+      it 'rejects the push' do
+        push_with_path(key, old_path_to_repo)
+
+        expect(response).to have_http_status(200)
+        expect(json_response['status']).to be_falsey
+        expect(json_response['message']).to eq(project_moved_message)
+      end
+
+      it 'rejects the SSH pull' do
+        pull_with_path(key, old_path_to_repo)
+
+        expect(response).to have_http_status(200)
+        expect(json_response['status']).to be_falsey
+        expect(json_response['message']).to eq(project_moved_message)
+      end
+    end
   end
 
   describe 'GET /internal/merge_request_urls' do
@@ -587,6 +621,17 @@ describe API::Internal do
     )
   end
 
+  def pull_with_path(key, path_to_repo, protocol = 'ssh')
+    post(
+      api("/internal/allowed"),
+      key_id: key.id,
+      project: path_to_repo,
+      action: 'git-upload-pack',
+      secret_token: secret_token,
+      protocol: protocol
+    )
+  end
+
   def push(key, project, protocol = 'ssh', env: nil)
     post(
       api("/internal/allowed"),
@@ -600,6 +645,19 @@ describe API::Internal do
     )
   end
 
+  def push_with_path(key, path_to_repo, protocol = 'ssh', env: nil)
+    post(
+      api("/internal/allowed"),
+      changes: 'd14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/master',
+      key_id: key.id,
+      project: path_to_repo,
+      action: 'git-receive-pack',
+      secret_token: secret_token,
+      protocol: protocol,
+      env: env
+    )
+  end
+
   def archive(key, project)
     post(
       api("/internal/allowed"),
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 16e5efb2f5b444776ac6e1f6a0074fe790e47a4a..4d0bd67c571a5ee7a70091cacb7bc6bf7e63bac9 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -334,14 +334,13 @@ describe API::MergeRequests do
              target_branch: 'master',
              author: user,
              labels: 'label, label2',
-             milestone_id: milestone.id,
-             remove_source_branch: true
+             milestone_id: milestone.id
 
         expect(response).to have_http_status(201)
         expect(json_response['title']).to eq('Test merge_request')
         expect(json_response['labels']).to eq(%w(label label2))
         expect(json_response['milestone']['id']).to eq(milestone.id)
-        expect(json_response['force_remove_source_branch']).to be_truthy
+        expect(json_response['force_remove_source_branch']).to be_falsy
       end
 
       it "returns 422 when source_branch equals target_branch" do
@@ -404,6 +403,27 @@ describe API::MergeRequests do
           expect(response).to have_http_status(409)
         end
       end
+
+      context 'accepts remove_source_branch parameter' do
+        let(:params) do
+          { title: 'Test merge_request',
+            source_branch: 'markdown',
+            target_branch: 'master',
+            author: user }
+        end
+
+        it 'sets force_remove_source_branch to false' do
+          post api("/projects/#{project.id}/merge_requests", user), params.merge(remove_source_branch: false)
+
+          expect(json_response['force_remove_source_branch']).to be_falsy
+        end
+
+        it 'sets force_remove_source_branch to true' do
+          post api("/projects/#{project.id}/merge_requests", user), params.merge(remove_source_branch: true)
+
+          expect(json_response['force_remove_source_branch']).to be_truthy
+        end
+      end
     end
 
     context 'forked projects' do
@@ -540,8 +560,8 @@ describe API::MergeRequests do
     end
 
     it "returns 406 if branch can't be merged" do
-      allow_any_instance_of(MergeRequest).
-        to receive(:can_be_merged?).and_return(false)
+      allow_any_instance_of(MergeRequest)
+        .to receive(:can_be_merged?).and_return(false)
 
       put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user)
 
diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb
index 40934c25afc6cd5033a8e8a27d96e085b751e76e..ab5ea3e8f2c42dba76c07240d33286963d116cbb 100644
--- a/spec/requests/api/milestones_spec.rb
+++ b/spec/requests/api/milestones_spec.rb
@@ -5,6 +5,9 @@ describe API::Milestones do
   let!(:project) { create(:empty_project, namespace: user.namespace ) }
   let!(:closed_milestone) { create(:closed_milestone, project: project, title: 'version1', description: 'closed milestone') }
   let!(:milestone) { create(:milestone, project: project, title: 'version2', description: 'open milestone') }
+  let(:label_1) { create(:label, title: 'label_1', project: project, priority: 1) }
+  let(:label_2) { create(:label, title: 'label_2', project: project, priority: 2) }
+  let(:label_3) { create(:label, title: 'label_3', project: project) }
 
   before do
     project.team << [user, :developer]
@@ -228,6 +231,18 @@ describe API::Milestones do
       expect(json_response.first['milestone']['title']).to eq(milestone.title)
     end
 
+    it 'returns project issues sorted by label priority' do
+      issue_1 = create(:labeled_issue, project: project, milestone: milestone, labels: [label_3])
+      issue_2 = create(:labeled_issue, project: project, milestone: milestone, labels: [label_1])
+      issue_3 = create(:labeled_issue, project: project, milestone: milestone, labels: [label_2])
+
+      get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user)
+
+      expect(json_response.first['id']).to eq(issue_2.id)
+      expect(json_response.second['id']).to eq(issue_3.id)
+      expect(json_response.third['id']).to eq(issue_1.id)
+    end
+
     it 'matches V4 response schema for a list of issues' do
       get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user)
 
@@ -244,8 +259,8 @@ describe API::Milestones do
     describe 'confidential issues' do
       let(:public_project) { create(:empty_project, :public) }
       let(:milestone) { create(:milestone, project: public_project) }
-      let(:issue) { create(:issue, project: public_project, position: 2) }
-      let(:confidential_issue) { create(:issue, confidential: true, project: public_project, position: 1) }
+      let(:issue) { create(:issue, project: public_project) }
+      let(:confidential_issue) { create(:issue, confidential: true, project: public_project) }
 
       before do
         public_project.team << [user, :developer]
@@ -285,7 +300,10 @@ describe API::Milestones do
         expect(json_response.map { |issue| issue['id'] }).to include(issue.id)
       end
 
-      it 'returns issues ordered by position asc' do
+      it 'returns issues ordered by label priority' do
+        issue.labels << label_2
+        confidential_issue.labels << label_1
+
         get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", user)
 
         expect(response).to have_http_status(200)
@@ -299,8 +317,8 @@ describe API::Milestones do
   end
 
   describe 'GET /projects/:id/milestones/:milestone_id/merge_requests' do
-    let(:merge_request) { create(:merge_request, source_project: project, position: 2) }
-    let(:another_merge_request) { create(:merge_request, :simple, source_project: project, position: 1) }
+    let(:merge_request) { create(:merge_request, source_project: project) }
+    let(:another_merge_request) { create(:merge_request, :simple, source_project: project) }
 
     before do
       milestone.merge_requests << merge_request
@@ -318,6 +336,18 @@ describe API::Milestones do
       expect(json_response.first['milestone']['title']).to eq(milestone.title)
     end
 
+    it 'returns project merge_requests sorted by label priority' do
+      merge_request_1 = create(:labeled_merge_request, source_branch: 'branch_1', source_project: project, milestone: milestone, labels: [label_2])
+      merge_request_2 = create(:labeled_merge_request, source_branch: 'branch_2', source_project: project, milestone: milestone, labels: [label_1])
+      merge_request_3 = create(:labeled_merge_request, source_branch: 'branch_3', source_project: project, milestone: milestone, labels: [label_3])
+
+      get api("/projects/#{project.id}/milestones/#{milestone.id}/merge_requests", user)
+
+      expect(json_response.first['id']).to eq(merge_request_2.id)
+      expect(json_response.second['id']).to eq(merge_request_1.id)
+      expect(json_response.third['id']).to eq(merge_request_3.id)
+    end
+
     it 'returns a 404 error if milestone id not found' do
       get api("/projects/#{project.id}/milestones/1234/merge_requests", user)
 
@@ -339,6 +369,8 @@ describe API::Milestones do
 
     it 'returns merge_requests ordered by position asc' do
       milestone.merge_requests << another_merge_request
+      another_merge_request.labels << label_1
+      merge_request.labels << label_2
 
       get api("/projects/#{project.id}/milestones/#{milestone.id}/merge_requests", user)
 
diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb
index 3bf16a3ae270fa42e9deb566bbe24bcb8e6d263d..26cf653ca8e4dd005f6a2a234fffb8e191c14eb9 100644
--- a/spec/requests/api/namespaces_spec.rb
+++ b/spec/requests/api/namespaces_spec.rb
@@ -15,6 +15,20 @@ describe API::Namespaces do
     end
 
     context "when authenticated as admin" do
+      it "returns correct attributes" do
+        get api("/namespaces", admin)
+
+        group_kind_json_response = json_response.find { |resource| resource['kind'] == 'group' }
+        user_kind_json_response = json_response.find { |resource| resource['kind'] == 'user' }
+
+        expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
+        expect(group_kind_json_response.keys).to contain_exactly('id', 'kind', 'name', 'path', 'full_path',
+                                                                 'parent_id', 'members_count_with_descendants')
+
+        expect(user_kind_json_response.keys).to contain_exactly('id', 'kind', 'name', 'path', 'full_path', 'parent_id')
+      end
+
       it "admin: returns an array of all namespaces" do
         get api("/namespaces", admin)
 
@@ -37,6 +51,27 @@ describe API::Namespaces do
     end
 
     context "when authenticated as a regular user" do
+      it "returns correct attributes when user can admin group" do
+        group1.add_owner(user)
+
+        get api("/namespaces", user)
+
+        owned_group_response = json_response.find { |resource| resource['id'] == group1.id }
+
+        expect(owned_group_response.keys).to contain_exactly('id', 'kind', 'name', 'path', 'full_path',
+                                                             'parent_id', 'members_count_with_descendants')
+      end
+
+      it "returns correct attributes when user cannot admin group" do
+        group1.add_guest(user)
+
+        get api("/namespaces", user)
+
+        guest_group_response = json_response.find { |resource| resource['id'] == group1.id }
+
+        expect(guest_group_response.keys).to contain_exactly('id', 'kind', 'name', 'path', 'full_path', 'parent_id')
+      end
+
       it "user: returns an array of namespaces" do
         get api("/namespaces", user)
 
diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb
index 03f2b5950ee7a1aa84939cc7622f3e51be7cbeb0..4701ad585c969ec362a5a2a8e2adb336b00e59a5 100644
--- a/spec/requests/api/notes_spec.rb
+++ b/spec/requests/api/notes_spec.rb
@@ -13,8 +13,8 @@ describe API::Notes do
   # For testing the cross-reference of a private issue in a public issue
   let(:private_user)    { create(:user) }
   let(:private_project) do
-    create(:empty_project, namespace: private_user.namespace).
-    tap { |p| p.team << [private_user, :master] }
+    create(:empty_project, namespace: private_user.namespace)
+    .tap { |p| p.team << [private_user, :master] }
   end
   let(:private_issue)    { create(:issue, project: private_project) }
 
diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb
index 4d4631322b1659ad6158257417c801413e881223..518639f45a26d4f0d5ce2794cafc7d6d4ac4caa1 100644
--- a/spec/requests/api/project_snippets_spec.rb
+++ b/spec/requests/api/project_snippets_spec.rb
@@ -102,23 +102,23 @@ describe API::ProjectSnippets do
 
       context 'when the snippet is private' do
         it 'creates the snippet' do
-          expect { create_snippet(project, visibility: 'private') }.
-            to change { Snippet.count }.by(1)
+          expect { create_snippet(project, visibility: 'private') }
+            .to change { Snippet.count }.by(1)
         end
       end
 
       context 'when the snippet is public' do
         it 'rejects the snippet' do
-          expect { create_snippet(project, visibility: 'public') }.
-            not_to change { Snippet.count }
+          expect { create_snippet(project, visibility: 'public') }
+            .not_to change { Snippet.count }
 
           expect(response).to have_http_status(400)
           expect(json_response['message']).to eq({ "error" => "Spam detected" })
         end
 
         it 'creates a spam log' do
-          expect { create_snippet(project, visibility: 'public') }.
-            to change { SpamLog.count }.by(1)
+          expect { create_snippet(project, visibility: 'public') }
+            .to change { SpamLog.count }.by(1)
         end
       end
     end
@@ -166,8 +166,8 @@ describe API::ProjectSnippets do
         let(:visibility_level) { Snippet::PRIVATE }
 
         it 'creates the snippet' do
-          expect { update_snippet(title: 'Foo') }.
-            to change { snippet.reload.title }.to('Foo')
+          expect { update_snippet(title: 'Foo') }
+            .to change { snippet.reload.title }.to('Foo')
         end
       end
 
@@ -175,13 +175,13 @@ describe API::ProjectSnippets do
         let(:visibility_level) { Snippet::PUBLIC }
 
         it 'rejects the snippet' do
-          expect { update_snippet(title: 'Foo') }.
-            not_to change { snippet.reload.title }
+          expect { update_snippet(title: 'Foo') }
+            .not_to change { snippet.reload.title }
         end
 
         it 'creates a spam log' do
-          expect { update_snippet(title: 'Foo') }.
-            to change { SpamLog.count }.by(1)
+          expect { update_snippet(title: 'Foo') }
+            .to change { SpamLog.count }.by(1)
         end
       end
 
@@ -189,16 +189,16 @@ describe API::ProjectSnippets do
         let(:visibility_level) { Snippet::PRIVATE }
 
         it 'rejects the snippet' do
-          expect { update_snippet(title: 'Foo', visibility: 'public') }.
-            not_to change { snippet.reload.title }
+          expect { update_snippet(title: 'Foo', visibility: 'public') }
+            .not_to change { snippet.reload.title }
 
           expect(response).to have_http_status(400)
           expect(json_response['message']).to eq({ "error" => "Spam detected" })
         end
 
         it 'creates a spam log' do
-          expect { update_snippet(title: 'Foo', visibility: 'public') }.
-            to change { SpamLog.count }.by(1)
+          expect { update_snippet(title: 'Foo', visibility: 'public') }
+            .to change { SpamLog.count }.by(1)
         end
       end
     end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index d92262a4c99fa54c6e5e6ed7ff4dceed4515dbef..8ac65ecccab037b470eda2933b31e02a47b7829b 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -288,15 +288,15 @@ describe API::Projects do
     context 'maximum number of projects reached' do
       it 'does not create new project and respond with 403' do
         allow_any_instance_of(User).to receive(:projects_limit_left).and_return(0)
-        expect { post api('/projects', user2), name: 'foo' }.
-          to change {Project.count}.by(0)
+        expect { post api('/projects', user2), name: 'foo' }
+          .to change {Project.count}.by(0)
         expect(response).to have_http_status(403)
       end
     end
 
     it 'creates new project without path but with name and returns 201' do
-      expect { post api('/projects', user), name: 'Foo Project' }.
-        to change { Project.count }.by(1)
+      expect { post api('/projects', user), name: 'Foo Project' }
+        .to change { Project.count }.by(1)
       expect(response).to have_http_status(201)
 
       project = Project.first
@@ -306,8 +306,8 @@ describe API::Projects do
     end
 
     it 'creates new project without name but with path and returns 201' do
-      expect { post api('/projects', user), path: 'foo_project' }.
-        to change { Project.count }.by(1)
+      expect { post api('/projects', user), path: 'foo_project' }
+        .to change { Project.count }.by(1)
       expect(response).to have_http_status(201)
 
       project = Project.first
@@ -317,8 +317,8 @@ describe API::Projects do
     end
 
     it 'creates new project with name and path and returns 201' do
-      expect { post api('/projects', user), path: 'path-project-Foo', name: 'Foo Project' }.
-        to change { Project.count }.by(1)
+      expect { post api('/projects', user), path: 'path-project-Foo', name: 'Foo Project' }
+        .to change { Project.count }.by(1)
       expect(response).to have_http_status(201)
 
       project = Project.first
@@ -347,7 +347,8 @@ describe API::Projects do
         wiki_enabled: false,
         only_allow_merge_if_pipeline_succeeds: false,
         request_access_enabled: true,
-        only_allow_merge_if_all_discussions_are_resolved: false
+        only_allow_merge_if_all_discussions_are_resolved: false,
+        ci_config_path: 'a/custom/path'
       })
 
       post api('/projects', user), project
@@ -491,8 +492,8 @@ describe API::Projects do
     end
 
     it 'creates new project with name and path and returns 201' do
-      expect { post api("/projects/user/#{user.id}", admin), path: 'path-project-Foo', name: 'Foo Project' }.
-        to change { Project.count }.by(1)
+      expect { post api("/projects/user/#{user.id}", admin), path: 'path-project-Foo', name: 'Foo Project' }
+        .to change { Project.count }.by(1)
       expect(response).to have_http_status(201)
 
       project = Project.first
@@ -502,8 +503,8 @@ describe API::Projects do
     end
 
     it 'responds with 400 on failure and not project' do
-      expect { post api("/projects/user/#{user.id}", admin) }.
-        not_to change { Project.count }
+      expect { post api("/projects/user/#{user.id}", admin) }
+        .not_to change { Project.count }
 
       expect(response).to have_http_status(400)
       expect(json_response['error']).to eq('name is missing')
@@ -653,6 +654,7 @@ describe API::Projects do
         expect(json_response['star_count']).to be_present
         expect(json_response['forks_count']).to be_present
         expect(json_response['public_jobs']).to be_present
+        expect(json_response['ci_config_path']).to be_nil
         expect(json_response['shared_with_groups']).to be_an Array
         expect(json_response['shared_with_groups'].length).to eq(1)
         expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id)
@@ -698,7 +700,8 @@ describe API::Projects do
           'name' => user.namespace.name,
           'path' => user.namespace.path,
           'kind' => user.namespace.kind,
-          'full_path' => user.namespace.full_path
+          'full_path' => user.namespace.full_path,
+          'parent_id' => nil
         })
       end
 
@@ -740,8 +743,8 @@ describe API::Projects do
             get api("/projects", user)
 
             expect(response).to have_http_status(200)
-            expect(json_response.first['permissions']['project_access']['access_level']).
-            to eq(Gitlab::Access::MASTER)
+            expect(json_response.first['permissions']['project_access']['access_level'])
+            .to eq(Gitlab::Access::MASTER)
             expect(json_response.first['permissions']['group_access']).to be_nil
           end
         end
@@ -752,8 +755,8 @@ describe API::Projects do
             get api("/projects/#{project.id}", user)
 
             expect(response).to have_http_status(200)
-            expect(json_response['permissions']['project_access']['access_level']).
-            to eq(Gitlab::Access::MASTER)
+            expect(json_response['permissions']['project_access']['access_level'])
+            .to eq(Gitlab::Access::MASTER)
             expect(json_response['permissions']['group_access']).to be_nil
           end
         end
@@ -770,8 +773,8 @@ describe API::Projects do
 
             expect(response).to have_http_status(200)
             expect(json_response['permissions']['project_access']).to be_nil
-            expect(json_response['permissions']['group_access']['access_level']).
-            to eq(Gitlab::Access::OWNER)
+            expect(json_response['permissions']['group_access']['access_level'])
+            .to eq(Gitlab::Access::OWNER)
           end
         end
       end
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index d554c242916c7bbfb921b9bd08fec2f25e0be12a..ca5d98c78ef871f6fca2d88a52ae12555585cfb9 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -351,7 +351,8 @@ describe API::Runner do
           let(:expected_cache) do
             [{ 'key' => 'cache_key',
                'untracked' => false,
-               'paths' => ['vendor/*'] }]
+               'paths' => ['vendor/*'],
+               'policy' => 'pull-push' }]
           end
 
           it 'picks a job' do
@@ -414,8 +415,8 @@ describe API::Runner do
 
           context 'when concurrently updating a job' do
             before do
-              expect_any_instance_of(Ci::Build).to receive(:run!).
-                  and_raise(ActiveRecord::StaleObjectError.new(nil, nil))
+              expect_any_instance_of(Ci::Build).to receive(:run!)
+                  .and_raise(ActiveRecord::StaleObjectError.new(nil, nil))
             end
 
             it 'returns a conflict' do
diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb
index 8741cbd4e80b8a02db75054969abcb16f971440b..b20a187acfe988ad9396cb3133891d67bda9f9d8 100644
--- a/spec/requests/api/snippets_spec.rb
+++ b/spec/requests/api/snippets_spec.rb
@@ -142,23 +142,23 @@ describe API::Snippets do
 
       context 'when the snippet is private' do
         it 'creates the snippet' do
-          expect { create_snippet(visibility: 'private') }.
-            to change { Snippet.count }.by(1)
+          expect { create_snippet(visibility: '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: 'public') }.
-            not_to change { Snippet.count }
+          expect { create_snippet(visibility: 'public') }
+            .not_to change { Snippet.count }
 
           expect(response).to have_http_status(400)
           expect(json_response['message']).to eq({ "error" => "Spam detected" })
         end
 
         it 'creates a spam log' do
-          expect { create_snippet(visibility: 'public') }.
-            to change { SpamLog.count }.by(1)
+          expect { create_snippet(visibility: 'public') }
+            .to change { SpamLog.count }.by(1)
         end
       end
     end
@@ -216,8 +216,8 @@ describe API::Snippets do
         let(:visibility_level) { Snippet::PRIVATE }
 
         it 'updates the snippet' do
-          expect { update_snippet(title: 'Foo') }.
-            to change { snippet.reload.title }.to('Foo')
+          expect { update_snippet(title: 'Foo') }
+            .to change { snippet.reload.title }.to('Foo')
         end
       end
 
@@ -225,16 +225,16 @@ describe API::Snippets do
         let(:visibility_level) { Snippet::PUBLIC }
 
         it 'rejects the shippet' do
-          expect { update_snippet(title: 'Foo') }.
-            not_to change { snippet.reload.title }
+          expect { update_snippet(title: 'Foo') }
+            .not_to change { snippet.reload.title }
 
           expect(response).to have_http_status(400)
           expect(json_response['message']).to eq({ "error" => "Spam detected" })
         end
 
         it 'creates a spam log' do
-          expect { update_snippet(title: 'Foo') }.
-            to change { SpamLog.count }.by(1)
+          expect { update_snippet(title: 'Foo') }
+            .to change { SpamLog.count }.by(1)
         end
       end
 
@@ -242,13 +242,13 @@ describe API::Snippets do
         let(:visibility_level) { Snippet::PRIVATE }
 
         it 'rejects the snippet' do
-          expect { update_snippet(title: 'Foo', visibility: 'public') }.
-            not_to change { snippet.reload.title }
+          expect { update_snippet(title: 'Foo', visibility: 'public') }
+            .not_to change { snippet.reload.title }
         end
 
         it 'creates a spam log' do
-          expect { update_snippet(title: 'Foo', visibility: 'public') }.
-            to change { SpamLog.count }.by(1)
+          expect { update_snippet(title: 'Foo', visibility: 'public') }
+            .to change { SpamLog.count }.by(1)
         end
       end
     end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 9dc4b6972a6ab67c847832d21b3718fc421fcedc..70b94a09e6b635fa7be234afb1958399c1ec6526 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -11,11 +11,42 @@ describe API::Users do
   let(:not_existing_user_id) { (User.maximum('id') || 0 ) + 10 }
   let(:not_existing_pat_id) { (PersonalAccessToken.maximum('id') || 0 ) + 10 }
 
-  describe "GET /users" do
+  describe 'GET /users' do
     context "when unauthenticated" do
-      it "returns authentication error" do
+      it "returns authorization error when the `username` parameter is not passed" do
         get api("/users")
-        expect(response).to have_http_status(401)
+
+        expect(response).to have_http_status(403)
+      end
+
+      it "returns the user when a valid `username` parameter is passed" do
+        user = create(:user)
+
+        get api("/users"), username: user.username
+
+        expect(response).to have_http_status(200)
+        expect(json_response).to be_an Array
+        expect(json_response.size).to eq(1)
+        expect(json_response[0]['id']).to eq(user.id)
+        expect(json_response[0]['username']).to eq(user.username)
+      end
+
+      it "returns authorization error when the `username` parameter refers to an inaccessible user" do
+        user = create(:user)
+
+        stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
+
+        get api("/users"), username: user.username
+
+        expect(response).to have_http_status(403)
+      end
+
+      it "returns an empty response when an invalid `username` parameter is passed" do
+        get api("/users"), username: 'invalid'
+
+        expect(response).to have_http_status(200)
+        expect(json_response).to be_an Array
+        expect(json_response.size).to eq(0)
       end
     end
 
@@ -76,6 +107,12 @@ describe API::Users do
 
         expect(response).to have_http_status(403)
       end
+
+      it 'does not reveal the `is_admin` flag of the user' do
+        get api('/users', user)
+
+        expect(json_response.first.keys).not_to include 'is_admin'
+      end
     end
 
     context "when admin" do
@@ -92,6 +129,7 @@ describe API::Users do
         expect(json_response.first.keys).to include 'two_factor_enabled'
         expect(json_response.first.keys).to include 'last_sign_in_at'
         expect(json_response.first.keys).to include 'confirmed_at'
+        expect(json_response.first.keys).to include 'is_admin'
       end
 
       it "returns an array of external users" do
@@ -131,6 +169,7 @@ describe API::Users do
   describe "GET /users/:id" do
     it "returns a user by id" do
       get api("/users/#{user.id}", user)
+
       expect(response).to have_http_status(200)
       expect(json_response['username']).to eq(user.username)
     end
@@ -141,9 +180,22 @@ describe API::Users do
       expect(json_response['is_admin']).to be_nil
     end
 
-    it "returns a 401 if unauthenticated" do
-      get api("/users/9998")
-      expect(response).to have_http_status(401)
+    context 'for an anonymous user' do
+      it "returns a user by id" do
+        get api("/users/#{user.id}")
+
+        expect(response).to have_http_status(200)
+        expect(json_response['username']).to eq(user.username)
+      end
+
+      it "returns a 404 if the target user is present but inaccessible" do
+        allow(Ability).to receive(:allowed?).and_call_original
+        allow(Ability).to receive(:allowed?).with(nil, :read_user, user).and_return(false)
+
+        get api("/users/#{user.id}")
+
+        expect(response).to have_http_status(404)
+      end
     end
 
     it "returns a 404 error if user id not found" do
@@ -282,14 +334,14 @@ describe API::Users do
            bio: 'g' * 256,
            projects_limit: -1
       expect(response).to have_http_status(400)
-      expect(json_response['message']['password']).
-        to eq(['is too short (minimum is 8 characters)'])
-      expect(json_response['message']['bio']).
-        to eq(['is too long (maximum is 255 characters)'])
-      expect(json_response['message']['projects_limit']).
-        to eq(['must be greater than or equal to 0'])
-      expect(json_response['message']['username']).
-        to eq([Gitlab::PathRegex.namespace_format_message])
+      expect(json_response['message']['password'])
+        .to eq(['is too short (minimum is 8 characters)'])
+      expect(json_response['message']['bio'])
+        .to eq(['is too long (maximum is 255 characters)'])
+      expect(json_response['message']['projects_limit'])
+        .to eq(['must be greater than or equal to 0'])
+      expect(json_response['message']['username'])
+        .to eq([Gitlab::PathRegex.namespace_format_message])
     end
 
     it "is not available for non admin users" do
@@ -338,6 +390,14 @@ describe API::Users do
         expect(json_response['identities'].first['provider']).to eq('github')
       end
     end
+
+    context "scopes" do
+      let(:user) { admin }
+      let(:path) { '/users' }
+      let(:api_call) { method(:api) }
+
+      include_examples 'does not allow the "read_user" scope'
+    end
   end
 
   describe "GET /users/sign_up" do
@@ -357,6 +417,7 @@ describe API::Users do
 
     it "updates user with new bio" do
       put api("/users/#{user.id}", admin), { bio: 'new test bio' }
+
       expect(response).to have_http_status(200)
       expect(json_response['bio']).to eq('new test bio')
       expect(user.reload.bio).to eq('new test bio')
@@ -377,15 +438,34 @@ describe API::Users do
       expect(user.reload.organization).to eq('GitLab')
     end
 
+    it 'updates user with avatar' do
+      put api("/users/#{user.id}", admin), { avatar: fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
+
+      user.reload
+
+      expect(user.avatar).to be_present
+      expect(response).to have_http_status(200)
+      expect(json_response['avatar_url']).to include(user.avatar_path)
+    end
+
     it 'updates user with his own email' do
       put api("/users/#{user.id}", admin), email: user.email
+
       expect(response).to have_http_status(200)
       expect(json_response['email']).to eq(user.email)
       expect(user.reload.email).to eq(user.email)
     end
 
+    it 'updates user with a new email' do
+      put api("/users/#{user.id}", admin), email: 'new@email.com'
+
+      expect(response).to have_http_status(200)
+      expect(user.reload.notification_email).to eq('new@email.com')
+    end
+
     it 'updates user with his own username' do
       put api("/users/#{user.id}", admin), username: user.username
+
       expect(response).to have_http_status(200)
       expect(json_response['username']).to eq(user.username)
       expect(user.reload.username).to eq(user.username)
@@ -393,12 +473,14 @@ describe API::Users do
 
     it "updates user's existing identity" do
       put api("/users/#{omniauth_user.id}", admin), provider: 'ldapmain', extern_uid: '654321'
+
       expect(response).to have_http_status(200)
       expect(omniauth_user.reload.identities.first.extern_uid).to eq('654321')
     end
 
     it 'updates user with new identity' do
       put api("/users/#{user.id}", admin), provider: 'github', extern_uid: 'john'
+
       expect(response).to have_http_status(200)
       expect(user.reload.identities.first.extern_uid).to eq('john')
       expect(user.reload.identities.first.provider).to eq('github')
@@ -406,12 +488,14 @@ describe API::Users do
 
     it "updates admin status" do
       put api("/users/#{user.id}", admin), { admin: true }
+
       expect(response).to have_http_status(200)
       expect(user.reload.admin).to eq(true)
     end
 
     it "updates external status" do
       put api("/users/#{user.id}", admin), { external: true }
+
       expect(response.status).to eq 200
       expect(json_response['external']).to eq(true)
       expect(user.reload.external?).to be_truthy
@@ -419,6 +503,7 @@ describe API::Users do
 
     it "does not update admin status" do
       put api("/users/#{admin_user.id}", admin), { can_create_group: false }
+
       expect(response).to have_http_status(200)
       expect(admin_user.reload.admin).to eq(true)
       expect(admin_user.can_create_group).to eq(false)
@@ -426,6 +511,7 @@ describe API::Users do
 
     it "does not allow invalid update" do
       put api("/users/#{user.id}", admin), { email: 'invalid email' }
+
       expect(response).to have_http_status(400)
       expect(user.reload.email).not_to eq('invalid email')
     end
@@ -442,6 +528,7 @@ describe API::Users do
 
     it "returns 404 for non-existing user" do
       put api("/users/999999", admin), { bio: 'update should fail' }
+
       expect(response).to have_http_status(404)
       expect(json_response['message']).to eq('404 User Not Found')
     end
@@ -461,14 +548,14 @@ describe API::Users do
           bio: 'g' * 256,
           projects_limit: -1
       expect(response).to have_http_status(400)
-      expect(json_response['message']['password']).
-        to eq(['is too short (minimum is 8 characters)'])
-      expect(json_response['message']['bio']).
-        to eq(['is too long (maximum is 255 characters)'])
-      expect(json_response['message']['projects_limit']).
-        to eq(['must be greater than or equal to 0'])
-      expect(json_response['message']['username']).
-        to eq([Gitlab::PathRegex.namespace_format_message])
+      expect(json_response['message']['password'])
+        .to eq(['is too short (minimum is 8 characters)'])
+      expect(json_response['message']['bio'])
+        .to eq(['is too long (maximum is 255 characters)'])
+      expect(json_response['message']['projects_limit'])
+        .to eq(['must be greater than or equal to 0'])
+      expect(json_response['message']['username'])
+        .to eq([Gitlab::PathRegex.namespace_format_message])
     end
 
     it 'returns 400 if provider is missing for identity update' do
@@ -492,6 +579,7 @@ describe API::Users do
 
       it 'returns 409 conflict error if email address exists' do
         put api("/users/#{@user.id}", admin), email: 'test@example.com'
+
         expect(response).to have_http_status(409)
         expect(@user.reload.email).to eq(@user.email)
       end
@@ -499,6 +587,7 @@ describe API::Users do
       it 'returns 409 conflict error if username taken' do
         @user_id = User.all.last.id
         put api("/users/#{@user.id}", admin), username: 'test'
+
         expect(response).to have_http_status(409)
         expect(@user.reload.username).to eq(@user.username)
       end
@@ -806,6 +895,13 @@ describe API::Users do
         expect(response).to match_response_schema('public_api/v4/user/public')
         expect(json_response['id']).to eq(user.id)
       end
+
+      context "scopes" do
+        let(:path) { "/user" }
+        let(:api_call) { method(:api) }
+
+        include_examples 'allows the "read_user" scope'
+      end
     end
 
     context 'with admin' do
@@ -875,6 +971,13 @@ describe API::Users do
         expect(json_response).to be_an Array
         expect(json_response.first["title"]).to eq(key.title)
       end
+
+      context "scopes" do
+        let(:path) { "/user/keys" }
+        let(:api_call) { method(:api) }
+
+        include_examples 'allows the "read_user" scope'
+      end
     end
   end
 
@@ -908,6 +1011,13 @@ describe API::Users do
 
       expect(response).to have_http_status(404)
     end
+
+    context "scopes" do
+      let(:path) { "/user/keys/#{key.id}" }
+      let(:api_call) { method(:api) }
+
+      include_examples 'allows the "read_user" scope'
+    end
   end
 
   describe "POST /user/keys" do
@@ -997,6 +1107,13 @@ describe API::Users do
         expect(json_response).to be_an Array
         expect(json_response.first["email"]).to eq(email.email)
       end
+
+      context "scopes" do
+        let(:path) { "/user/emails" }
+        let(:api_call) { method(:api) }
+
+        include_examples 'allows the "read_user" scope'
+      end
     end
   end
 
@@ -1029,6 +1146,13 @@ describe API::Users do
 
       expect(response).to have_http_status(404)
     end
+
+    context "scopes" do
+      let(:path) { "/user/emails/#{email.id}" }
+      let(:api_call) { method(:api) }
+
+      include_examples 'allows the "read_user" scope'
+    end
   end
 
   describe "POST /user/emails" do
diff --git a/spec/requests/api/v3/files_spec.rb b/spec/requests/api/v3/files_spec.rb
index 378ca1720ffca683303dc98d75895a3409bc72f9..8b2d165c7633d2284b362f51b9f598a9cb4a42e7 100644
--- a/spec/requests/api/v3/files_spec.rb
+++ b/spec/requests/api/v3/files_spec.rb
@@ -126,8 +126,8 @@ describe API::V3::Files do
     end
 
     it "returns a 400 if editor fails to create file" do
-      allow_any_instance_of(Repository).to receive(:create_file).
-        and_raise(Repository::CommitError, 'Cannot create file')
+      allow_any_instance_of(Repository).to receive(:create_file)
+        .and_raise(Repository::CommitError, 'Cannot create file')
 
       post v3_api("/projects/#{project.id}/repository/files", user), valid_params
 
diff --git a/spec/requests/api/v3/groups_spec.rb b/spec/requests/api/v3/groups_spec.rb
index 98e8c954909d6a29ba35aa8ca874624bbd6bcfca..63c5707b2e452a8a3426b2f1adde6a265cac44bb 100644
--- a/spec/requests/api/v3/groups_spec.rb
+++ b/spec/requests/api/v3/groups_spec.rb
@@ -505,8 +505,8 @@ describe API::V3::Groups do
     let(:project_path) { "#{project.namespace.path}%2F#{project.path}" }
 
     before(:each) do
-      allow_any_instance_of(Projects::TransferService).
-        to receive(:execute).and_return(true)
+      allow_any_instance_of(Projects::TransferService)
+        .to receive(:execute).and_return(true)
     end
 
     context "when authenticated as user" do
diff --git a/spec/requests/api/v3/merge_requests_spec.rb b/spec/requests/api/v3/merge_requests_spec.rb
index f6ff96be5665074b7546cc70b9d8caf185c04743..4f9e63f2ace251eb8a4e4c2436b2d2b231e74790 100644
--- a/spec/requests/api/v3/merge_requests_spec.rb
+++ b/spec/requests/api/v3/merge_requests_spec.rb
@@ -432,8 +432,8 @@ describe API::MergeRequests do
     end
 
     it "returns 406 if branch can't be merged" do
-      allow_any_instance_of(MergeRequest).
-        to receive(:can_be_merged?).and_return(false)
+      allow_any_instance_of(MergeRequest)
+        .to receive(:can_be_merged?).and_return(false)
 
       put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
 
diff --git a/spec/requests/api/v3/notes_spec.rb b/spec/requests/api/v3/notes_spec.rb
index 2bae4a609315eef90b754ef23073d863d44a69a3..b5f98a9a545814af33d6335625c53535504d9b6e 100644
--- a/spec/requests/api/v3/notes_spec.rb
+++ b/spec/requests/api/v3/notes_spec.rb
@@ -13,8 +13,8 @@ describe API::V3::Notes do
   # For testing the cross-reference of a private issue in a public issue
   let(:private_user)    { create(:user) }
   let(:private_project) do
-    create(:empty_project, namespace: private_user.namespace).
-    tap { |p| p.team << [private_user, :master] }
+    create(:empty_project, namespace: private_user.namespace)
+    .tap { |p| p.team << [private_user, :master] }
   end
   let(:private_issue)    { create(:issue, project: private_project) }
 
diff --git a/spec/requests/api/v3/project_snippets_spec.rb b/spec/requests/api/v3/project_snippets_spec.rb
index 365e7365fda484ed6d07f4ebbeddd386a79cf47d..1950c64c6900d1d3243bc01f28b9dcbaa73f9dc7 100644
--- a/spec/requests/api/v3/project_snippets_spec.rb
+++ b/spec/requests/api/v3/project_snippets_spec.rb
@@ -85,23 +85,23 @@ describe API::ProjectSnippets 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)
+          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 { create_snippet(project, visibility_level: Snippet::PUBLIC) }
+            .not_to change { Snippet.count }
 
           expect(response).to have_http_status(400)
           expect(json_response['message']).to eq({ "error" => "Spam detected" })
         end
 
         it 'creates a spam log' do
-          expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
-            to change { SpamLog.count }.by(1)
+          expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }
+            .to change { SpamLog.count }.by(1)
         end
       end
     end
@@ -147,8 +147,8 @@ describe API::ProjectSnippets do
         let(:visibility_level) { Snippet::PRIVATE }
 
         it 'creates the snippet' do
-          expect { update_snippet(title: 'Foo') }.
-            to change { snippet.reload.title }.to('Foo')
+          expect { update_snippet(title: 'Foo') }
+            .to change { snippet.reload.title }.to('Foo')
         end
       end
 
@@ -156,13 +156,13 @@ describe API::ProjectSnippets do
         let(:visibility_level) { Snippet::PUBLIC }
 
         it 'rejects the snippet' do
-          expect { update_snippet(title: 'Foo') }.
-            not_to change { snippet.reload.title }
+          expect { update_snippet(title: 'Foo') }
+            .not_to change { snippet.reload.title }
         end
 
         it 'creates a spam log' do
-          expect { update_snippet(title: 'Foo') }.
-            to change { SpamLog.count }.by(1)
+          expect { update_snippet(title: 'Foo') }
+            .to change { SpamLog.count }.by(1)
         end
       end
 
@@ -170,16 +170,16 @@ describe API::ProjectSnippets do
         let(:visibility_level) { Snippet::PRIVATE }
 
         it 'rejects the snippet' do
-          expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }.
-            not_to change { snippet.reload.title }
+          expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }
+            .not_to change { snippet.reload.title }
 
           expect(response).to have_http_status(400)
           expect(json_response['message']).to eq({ "error" => "Spam detected" })
         end
 
         it 'creates a spam log' do
-          expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }.
-            to change { SpamLog.count }.by(1)
+          expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }
+            .to change { SpamLog.count }.by(1)
         end
       end
     end
diff --git a/spec/requests/api/v3/projects_spec.rb b/spec/requests/api/v3/projects_spec.rb
index 47cca4275afad0a96fe11a877fd02fdb6d95d812..af44ffa23317833c92685f6b16a0e0dbe267e692 100644
--- a/spec/requests/api/v3/projects_spec.rb
+++ b/spec/requests/api/v3/projects_spec.rb
@@ -124,6 +124,36 @@ describe API::V3::Projects do
         end
       end
 
+      context 'and using archived' do
+        let!(:archived_project) { create(:empty_project, creator_id: user.id, namespace: user.namespace, archived: true) }
+
+        it 'returns archived project' do
+          get v3_api('/projects?archived=true', user)
+
+          expect(response).to have_http_status(200)
+          expect(json_response).to be_an Array
+          expect(json_response.length).to eq(1)
+          expect(json_response.first['id']).to eq(archived_project.id)
+        end
+
+        it 'returns non-archived project' do
+          get v3_api('/projects?archived=false', user)
+
+          expect(response).to have_http_status(200)
+          expect(json_response).to be_an Array
+          expect(json_response.length).to eq(1)
+          expect(json_response.first['id']).to eq(project.id)
+        end
+
+        it 'returns all project' do
+          get v3_api('/projects', user)
+
+          expect(response).to have_http_status(200)
+          expect(json_response).to be_an Array
+          expect(json_response.length).to eq(2)
+        end
+      end
+
       context 'and using sorting' do
         before do
           project2
@@ -301,15 +331,15 @@ describe API::V3::Projects do
     context 'maximum number of projects reached' do
       it 'does not create new project and respond with 403' do
         allow_any_instance_of(User).to receive(:projects_limit_left).and_return(0)
-        expect { post v3_api('/projects', user2), name: 'foo' }.
-          to change {Project.count}.by(0)
+        expect { post v3_api('/projects', user2), name: 'foo' }
+          .to change {Project.count}.by(0)
         expect(response).to have_http_status(403)
       end
     end
 
     it 'creates new project without path but with name and returns 201' do
-      expect { post v3_api('/projects', user), name: 'Foo Project' }.
-        to change { Project.count }.by(1)
+      expect { post v3_api('/projects', user), name: 'Foo Project' }
+        .to change { Project.count }.by(1)
       expect(response).to have_http_status(201)
 
       project = Project.first
@@ -319,8 +349,8 @@ describe API::V3::Projects do
     end
 
     it 'creates new project without name but with path and returns 201' do
-      expect { post v3_api('/projects', user), path: 'foo_project' }.
-        to change { Project.count }.by(1)
+      expect { post v3_api('/projects', user), path: 'foo_project' }
+        .to change { Project.count }.by(1)
       expect(response).to have_http_status(201)
 
       project = Project.first
@@ -330,8 +360,8 @@ describe API::V3::Projects do
     end
 
     it 'creates new project name and path and returns 201' do
-      expect { post v3_api('/projects', user), path: 'foo-Project', name: 'Foo Project' }.
-        to change { Project.count }.by(1)
+      expect { post v3_api('/projects', user), path: 'foo-Project', name: 'Foo Project' }
+        .to change { Project.count }.by(1)
       expect(response).to have_http_status(201)
 
       project = Project.first
@@ -489,8 +519,8 @@ describe API::V3::Projects do
     end
 
     it 'responds with 400 on failure and not project' do
-      expect { post v3_api("/projects/user/#{user.id}", admin) }.
-        not_to change { Project.count }
+      expect { post v3_api("/projects/user/#{user.id}", admin) }
+        .not_to change { Project.count }
 
       expect(response).to have_http_status(400)
       expect(json_response['error']).to eq('name is missing')
@@ -704,7 +734,8 @@ describe API::V3::Projects do
           'name' => user.namespace.name,
           'path' => user.namespace.path,
           'kind' => user.namespace.kind,
-          'full_path' => user.namespace.full_path
+          'full_path' => user.namespace.full_path,
+          'parent_id' => nil
         })
       end
 
@@ -716,8 +747,8 @@ describe API::V3::Projects do
             get v3_api("/projects", user)
 
             expect(response).to have_http_status(200)
-            expect(json_response.first['permissions']['project_access']['access_level']).
-            to eq(Gitlab::Access::MASTER)
+            expect(json_response.first['permissions']['project_access']['access_level'])
+            .to eq(Gitlab::Access::MASTER)
             expect(json_response.first['permissions']['group_access']).to be_nil
           end
         end
@@ -728,8 +759,8 @@ describe API::V3::Projects do
             get v3_api("/projects/#{project.id}", user)
 
             expect(response).to have_http_status(200)
-            expect(json_response['permissions']['project_access']['access_level']).
-            to eq(Gitlab::Access::MASTER)
+            expect(json_response['permissions']['project_access']['access_level'])
+            .to eq(Gitlab::Access::MASTER)
             expect(json_response['permissions']['group_access']).to be_nil
           end
         end
@@ -744,8 +775,8 @@ describe API::V3::Projects do
 
             expect(response).to have_http_status(200)
             expect(json_response['permissions']['project_access']).to be_nil
-            expect(json_response['permissions']['group_access']['access_level']).
-            to eq(Gitlab::Access::OWNER)
+            expect(json_response['permissions']['group_access']['access_level'])
+            .to eq(Gitlab::Access::OWNER)
           end
         end
       end
diff --git a/spec/requests/api/v3/snippets_spec.rb b/spec/requests/api/v3/snippets_spec.rb
index 4f02b7b1a5483203dd4451a038bedd479d13d5b1..1bc2258ebd35ee647917fca117dee2ef0320f220 100644
--- a/spec/requests/api/v3/snippets_spec.rb
+++ b/spec/requests/api/v3/snippets_spec.rb
@@ -112,21 +112,21 @@ describe API::V3::Snippets do
 
       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)
+          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 { 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)
+          expect { create_snippet(visibility_level: Snippet::PUBLIC) }
+            .to change { SpamLog.count }.by(1)
         end
       end
     end
diff --git a/spec/requests/api/v3/users_spec.rb b/spec/requests/api/v3/users_spec.rb
index e9c57f7c6c37cefbd2cb3135326b77376b0e4d1f..de7499a4e43da0039b189ecc492176c35a51afbc 100644
--- a/spec/requests/api/v3/users_spec.rb
+++ b/spec/requests/api/v3/users_spec.rb
@@ -7,6 +7,38 @@ describe API::V3::Users do
   let(:email)   { create(:email, user: user) }
   let(:ldap_blocked_user) { create(:omniauth_user, provider: 'ldapmain', state: 'ldap_blocked') }
 
+  describe 'GET /users' do
+    context 'when authenticated' do
+      it 'returns an array of users' do
+        get v3_api('/users', user)
+
+        expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
+        expect(json_response).to be_an Array
+        username = user.username
+        expect(json_response.detect do |user|
+          user['username'] == username
+        end['username']).to eq(username)
+      end
+    end
+
+    context 'when authenticated as user' do
+      it 'does not reveal the `is_admin` flag of the user' do
+        get v3_api('/users', user)
+
+        expect(json_response.first.keys).not_to include 'is_admin'
+      end
+    end
+
+    context 'when authenticated as admin' do
+      it 'reveals the `is_admin` flag of the user' do
+        get v3_api('/users', admin)
+
+        expect(json_response.first.keys).to include 'is_admin'
+      end
+    end
+  end
+
   describe 'GET /user/:id/keys' do
     before { admin }
 
@@ -35,6 +67,19 @@ describe API::V3::Users do
         expect(json_response.first['title']).to eq(key.title)
       end
     end
+
+    context "scopes" do
+      let(:user) { admin }
+      let(:path) { "/users/#{user.id}/keys" }
+      let(:api_call) { method(:v3_api) }
+
+      before do
+        user.keys << key
+        user.save
+      end
+
+      include_examples 'allows the "read_user" scope'
+    end
   end
 
   describe 'GET /user/:id/emails' do
@@ -255,7 +300,7 @@ describe API::V3::Users do
     end
 
     it 'returns a 404 error if not found' do
-      get v3_api('/users/42/events', user)
+      get v3_api('/users/420/events', user)
 
       expect(response).to have_http_status(404)
       expect(json_response['message']).to eq('404 User Not Found')
@@ -280,5 +325,13 @@ describe API::V3::Users do
 
       expect(json_response['is_admin']).to be_nil
     end
+
+    context "scopes" do
+      let(:user) { admin }
+      let(:path) { '/users' }
+      let(:api_call) { method(:v3_api) }
+
+      include_examples 'does not allow the "read_user" scope'
+    end
   end
 end
diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb
index 83673864fe75db55399543c60c21ae0a087c6705..e0975024b803c522ce58bcab5a1a65361314c353 100644
--- a/spec/requests/api/variables_spec.rb
+++ b/spec/requests/api/variables_spec.rb
@@ -82,6 +82,17 @@ describe API::Variables do
         expect(json_response['protected']).to be_truthy
       end
 
+      it 'creates variable with optional attributes' do
+        expect do
+          post api("/projects/#{project.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2'
+        end.to change{project.variables.count}.by(1)
+
+        expect(response).to have_http_status(201)
+        expect(json_response['key']).to eq('TEST_VARIABLE_2')
+        expect(json_response['value']).to eq('VALUE_2')
+        expect(json_response['protected']).to be_falsey
+      end
+
       it 'does not allow to duplicate variable key' do
         expect do
           post api("/projects/#{project.id}/variables", user), key: variable.key, value: 'VALUE_2'
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index 83c675792f4cc3486ad3f6040b7c5ed81ee89587..c969d08d0ddd529e43c6470131f1594e8f56db00 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -91,8 +91,8 @@ describe Ci::API::Builds do
 
         context 'when concurrently updating build' do
           before do
-            expect_any_instance_of(Ci::Build).to receive(:run!).
-              and_raise(ActiveRecord::StaleObjectError.new(nil, nil))
+            expect_any_instance_of(Ci::Build).to receive(:run!)
+              .and_raise(ActiveRecord::StaleObjectError.new(nil, nil))
           end
 
           it 'returns a conflict' do
@@ -670,8 +670,8 @@ describe Ci::API::Builds do
                 build.reload
                 expect(response).to have_http_status(201)
                 expect(json_response['artifacts_expire_at']).not_to be_empty
-                expect(build.artifacts_expire_at).
-                  to be_within(5.minutes).of(7.days.from_now)
+                expect(build.artifacts_expire_at)
+                  .to be_within(5.minutes).of(7.days.from_now)
               end
             end
 
diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb
index dce78faefc9468487e3135100c28a4192d7c8632..185679e1a0fbd8b4563259768b8073f20e100f0e 100644
--- a/spec/requests/git_http_spec.rb
+++ b/spec/requests/git_http_spec.rb
@@ -316,6 +316,26 @@ describe 'Git HTTP requests', lib: true do
             it_behaves_like 'pushes require Basic HTTP Authentication'
           end
         end
+
+        context 'and the user requests a redirected path' do
+          let!(:redirect) { project.route.create_redirect('foo/bar') }
+          let(:path) { "#{redirect.path}.git" }
+          let(:project_moved_message) do
+            <<-MSG.strip_heredoc
+              Project '#{redirect.path}' was moved to '#{project.full_path}'.
+
+              Please update your Git remote and try again:
+
+                git remote set-url origin #{project.http_url_to_repo}
+            MSG
+          end
+
+          it 'downloads get status 404 with "project was moved" message' do
+            clone_get(path, {})
+            expect(response).to have_http_status(:not_found)
+            expect(response.body).to match(project_moved_message)
+          end
+        end
       end
 
       context "when the project is private" do
@@ -463,8 +483,8 @@ describe 'Git HTTP requests', lib: true do
                 context 'when LDAP is configured' do
                   before do
                     allow(Gitlab::LDAP::Config).to receive(:enabled?).and_return(true)
-                    allow_any_instance_of(Gitlab::LDAP::Authentication).
-                      to receive(:login).and_return(nil)
+                    allow_any_instance_of(Gitlab::LDAP::Authentication)
+                      .to receive(:login).and_return(nil)
                   end
 
                   it 'does not display the personal access token error message' do
@@ -505,6 +525,33 @@ describe 'Git HTTP requests', lib: true do
                   Rack::Attack::Allow2Ban.reset(ip, options)
                 end
               end
+
+              context 'and the user requests a redirected path' do
+                let!(:redirect) { project.route.create_redirect('foo/bar') }
+                let(:path) { "#{redirect.path}.git" }
+                let(:project_moved_message) do
+                  <<-MSG.strip_heredoc
+                    Project '#{redirect.path}' was moved to '#{project.full_path}'.
+
+                    Please update your Git remote and try again:
+
+                      git remote set-url origin #{project.http_url_to_repo}
+                  MSG
+                end
+
+                it 'downloads get status 404 with "project was moved" message' do
+                  clone_get(path, env)
+                  expect(response).to have_http_status(:not_found)
+                  expect(response.body).to match(project_moved_message)
+                end
+
+                it 'uploads get status 404 with "project was moved" message' do
+                  upload(path, env) do |response|
+                    expect(response).to have_http_status(:not_found)
+                    expect(response.body).to match(project_moved_message)
+                  end
+                end
+              end
             end
 
             context "when the user doesn't have access to the project" do
@@ -680,7 +727,7 @@ describe 'Git HTTP requests', lib: true do
         end
 
         context "POST git-receive-pack" do
-          it "failes to find a route" do
+          it "fails to find a route" do
             expect { push_post(project.path_with_namespace) }.to raise_error(ActionController::RoutingError)
           end
         end
diff --git a/spec/requests/projects/cycle_analytics_events_spec.rb b/spec/requests/projects/cycle_analytics_events_spec.rb
index d4d3c9478a002e8217cb53559a43a3c655bcee07..e78d2cfdb3371d2d2afa59a804ec50cd57c66698 100644
--- a/spec/requests/projects/cycle_analytics_events_spec.rb
+++ b/spec/requests/projects/cycle_analytics_events_spec.rb
@@ -21,7 +21,7 @@ describe 'cycle analytics events', api: true do
     end
 
     it 'lists the issue events' do
-      get namespace_project_cycle_analytics_issue_path(project.namespace, project, format: :json)
+      get project_cycle_analytics_issue_path(project, format: :json)
 
       first_issue_iid = project.issues.sort(:created_desc).pluck(:iid).first.to_s
 
@@ -30,7 +30,7 @@ describe 'cycle analytics events', api: true do
     end
 
     it 'lists the plan events' do
-      get namespace_project_cycle_analytics_plan_path(project.namespace, project, format: :json)
+      get project_cycle_analytics_plan_path(project, format: :json)
 
       first_mr_short_sha = project.merge_requests.sort(:created_asc).first.commits.first.short_id
 
@@ -39,7 +39,7 @@ describe 'cycle analytics events', api: true do
     end
 
     it 'lists the code events' do
-      get namespace_project_cycle_analytics_code_path(project.namespace, project, format: :json)
+      get project_cycle_analytics_code_path(project, format: :json)
 
       expect(json_response['events']).not_to be_empty
 
@@ -49,14 +49,14 @@ describe 'cycle analytics events', api: true do
     end
 
     it 'lists the test events' do
-      get namespace_project_cycle_analytics_test_path(project.namespace, project, format: :json)
+      get project_cycle_analytics_test_path(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)
+      get project_cycle_analytics_review_path(project, format: :json)
 
       first_mr_iid = project.merge_requests.sort(:created_desc).pluck(:iid).first.to_s
 
@@ -65,14 +65,14 @@ describe 'cycle analytics events', api: true do
     end
 
     it 'lists the staging events' do
-      get namespace_project_cycle_analytics_staging_path(project.namespace, project, format: :json)
+      get project_cycle_analytics_staging_path(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)
+      get project_cycle_analytics_production_path(project, format: :json)
 
       first_issue_iid = project.issues.sort(:created_desc).pluck(:iid).first.to_s
 
@@ -84,7 +84,7 @@ describe 'cycle analytics events', api: true do
       it 'lists the test events' do
         branch = project.merge_requests.first.source_branch
 
-        get namespace_project_cycle_analytics_test_path(project.namespace, project, format: :json, branch: branch)
+        get project_cycle_analytics_test_path(project, format: :json, branch: branch)
 
         expect(json_response['events']).not_to be_empty
         expect(json_response['events'].first['date']).not_to be_empty
@@ -97,19 +97,19 @@ describe 'cycle analytics events', api: true do
       end
 
       it 'does not list the test events' do
-        get namespace_project_cycle_analytics_test_path(project.namespace, project, format: :json)
+        get project_cycle_analytics_test_path(project, format: :json)
 
         expect(response).to have_http_status(:not_found)
       end
 
       it 'does not list the staging events' do
-        get namespace_project_cycle_analytics_staging_path(project.namespace, project, format: :json)
+        get project_cycle_analytics_staging_path(project, format: :json)
 
         expect(response).to have_http_status(:not_found)
       end
 
       it 'lists the issue events' do
-        get namespace_project_cycle_analytics_issue_path(project.namespace, project, format: :json)
+        get project_cycle_analytics_issue_path(project, format: :json)
 
         expect(response).to have_http_status(:ok)
       end
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index 95d40138fea70fb18de5c4bb0ab6359b49c51293..2f1c3c95e597451ae5b781eacbed6187c0dda65c 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -246,28 +246,13 @@ describe 'project routing' do
     end
   end
 
-  #               diffs_namespace_project_merge_request GET      /:namespace_id/:project_id/merge_requests/:id/diffs(.:format)               projects/merge_requests#diffs
-  #             commits_namespace_project_merge_request GET      /:namespace_id/:project_id/merge_requests/:id/commits(.:format)             projects/merge_requests#commits
-  #           merge_namespace_project_merge_request POST     /:namespace_id/:project_id/merge_requests/:id/merge(.:format)           projects/merge_requests#merge
-  #           ci_status_namespace_project_merge_request GET      /:namespace_id/:project_id/merge_requests/:id/ci_status(.:format)           projects/merge_requests#ci_status
-  # toggle_subscription_namespace_project_merge_request POST     /:namespace_id/:project_id/merge_requests/:id/toggle_subscription(.:format) projects/merge_requests#toggle_subscription
-  #        branch_from_namespace_project_merge_requests GET      /:namespace_id/:project_id/merge_requests/branch_from(.:format)             projects/merge_requests#branch_from
-  #          branch_to_namespace_project_merge_requests GET      /:namespace_id/:project_id/merge_requests/branch_to(.:format)               projects/merge_requests#branch_to
-  #    update_branches_namespace_project_merge_requests GET      /:namespace_id/:project_id/merge_requests/update_branches(.:format)         projects/merge_requests#update_branches
-  #                    namespace_project_merge_requests GET      /:namespace_id/:project_id/merge_requests(.:format)                         projects/merge_requests#index
-  #                                                     POST     /:namespace_id/:project_id/merge_requests(.:format)                         projects/merge_requests#create
-  #                 new_namespace_project_merge_request GET      /:namespace_id/:project_id/merge_requests/new(.:format)                     projects/merge_requests#new
-  #                edit_namespace_project_merge_request GET      /:namespace_id/:project_id/merge_requests/:id/edit(.:format)                projects/merge_requests#edit
-  #                     namespace_project_merge_request GET      /:namespace_id/:project_id/merge_requests/:id(.:format)                     projects/merge_requests#show
-  #                                                     PATCH    /:namespace_id/:project_id/merge_requests/:id(.:format)                     projects/merge_requests#update
-  #                                                     PUT      /:namespace_id/:project_id/merge_requests/:id(.:format)                     projects/merge_requests#update
   describe Projects::MergeRequestsController, 'routing' do
-    it 'to #diffs' do
-      expect(get('/gitlab/gitlabhq/merge_requests/1/diffs')).to route_to('projects/merge_requests#diffs', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
+    it 'to #commits' do
+      expect(get('/gitlab/gitlabhq/merge_requests/1/commits.json')).to route_to('projects/merge_requests#commits', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', format: 'json')
     end
 
-    it 'to #commits' do
-      expect(get('/gitlab/gitlabhq/merge_requests/1/commits')).to route_to('projects/merge_requests#commits', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
+    it 'to #pipelines' do
+      expect(get('/gitlab/gitlabhq/merge_requests/1/pipelines.json')).to route_to('projects/merge_requests#pipelines', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', format: 'json')
     end
 
     it 'to #merge' do
@@ -277,25 +262,59 @@ describe 'project routing' do
       )
     end
 
+    it 'to #show' do
+      expect(get('/gitlab/gitlabhq/merge_requests/1.diff')).to route_to('projects/merge_requests#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', format: 'diff')
+      expect(get('/gitlab/gitlabhq/merge_requests/1.patch')).to route_to('projects/merge_requests#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', format: 'patch')
+      expect(get('/gitlab/gitlabhq/merge_requests/1/diffs')).to route_to('projects/merge_requests#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', tab: 'diffs')
+      expect(get('/gitlab/gitlabhq/merge_requests/1/commits')).to route_to('projects/merge_requests#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', tab: 'commits')
+      expect(get('/gitlab/gitlabhq/merge_requests/1/pipelines')).to route_to('projects/merge_requests#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', tab: 'pipelines')
+    end
+
+    it_behaves_like 'RESTful project resources' do
+      let(:controller) { 'merge_requests' }
+      let(:actions) { [:index, :edit, :show, :update] }
+    end
+  end
+
+  describe Projects::MergeRequests::CreationsController, 'routing' do
+    it 'to #new' do
+      expect(get('/gitlab/gitlabhq/merge_requests/new')).to route_to('projects/merge_requests/creations#new', namespace_id: 'gitlab', project_id: 'gitlabhq')
+      expect(get('/gitlab/gitlabhq/merge_requests/new/diffs')).to route_to('projects/merge_requests/creations#new', namespace_id: 'gitlab', project_id: 'gitlabhq', tab: 'diffs')
+      expect(get('/gitlab/gitlabhq/merge_requests/new/pipelines')).to route_to('projects/merge_requests/creations#new', namespace_id: 'gitlab', project_id: 'gitlabhq', tab: 'pipelines')
+    end
+
+    it 'to #create' do
+      expect(post('/gitlab/gitlabhq/merge_requests')).to route_to('projects/merge_requests/creations#create', namespace_id: 'gitlab', project_id: 'gitlabhq')
+    end
+
     it 'to #branch_from' do
-      expect(get('/gitlab/gitlabhq/merge_requests/branch_from')).to route_to('projects/merge_requests#branch_from', namespace_id: 'gitlab', project_id: 'gitlabhq')
+      expect(get('/gitlab/gitlabhq/merge_requests/new/branch_from')).to route_to('projects/merge_requests/creations#branch_from', namespace_id: 'gitlab', project_id: 'gitlabhq')
     end
 
     it 'to #branch_to' do
-      expect(get('/gitlab/gitlabhq/merge_requests/branch_to')).to route_to('projects/merge_requests#branch_to', namespace_id: 'gitlab', project_id: 'gitlabhq')
+      expect(get('/gitlab/gitlabhq/merge_requests/new/branch_to')).to route_to('projects/merge_requests/creations#branch_to', namespace_id: 'gitlab', project_id: 'gitlabhq')
     end
 
-    it 'to #show' do
-      expect(get('/gitlab/gitlabhq/merge_requests/1.diff')).to route_to('projects/merge_requests#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', format: 'diff')
-      expect(get('/gitlab/gitlabhq/merge_requests/1.patch')).to route_to('projects/merge_requests#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', format: 'patch')
+    it 'to #pipelines' do
+      expect(get('/gitlab/gitlabhq/merge_requests/new/pipelines.json')).to route_to('projects/merge_requests/creations#pipelines', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'json')
     end
 
-    it_behaves_like 'RESTful project resources' do
-      let(:controller) { 'merge_requests' }
-      let(:actions) { [:index, :create, :new, :edit, :show, :update] }
+    it 'to #diffs' do
+      expect(get('/gitlab/gitlabhq/merge_requests/new/diffs.json')).to route_to('projects/merge_requests/creations#diffs', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'json')
+    end
+  end
+
+  describe Projects::MergeRequests::DiffsController, 'routing' do
+    it 'to #show' do
+      expect(get('/gitlab/gitlabhq/merge_requests/1/diffs.json')).to route_to('projects/merge_requests/diffs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', format: 'json')
     end
   end
 
+  describe Projects::MergeRequests::ConflictsController, 'routing' do
+    it 'to #show' do
+      expect(get('/gitlab/gitlabhq/merge_requests/1/conflicts')).to route_to('projects/merge_requests/conflicts#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
+    end
+  end
   #  raw_project_snippet GET    /:project_id/snippets/:id/raw(.:format)  snippets#raw
   #     project_snippets GET    /:project_id/snippets(.:format)          snippets#index
   #                      POST   /:project_id/snippets(.:format)          snippets#create
diff --git a/spec/rubocop/cop/migration/update_column_in_batches_spec.rb b/spec/rubocop/cop/migration/update_column_in_batches_spec.rb
index 968dcd6232ef55f12ce8696b1d4c5a8c85ec902a..38b8f439a5585f94a6638f91d06822265b3da773 100644
--- a/spec/rubocop/cop/migration/update_column_in_batches_spec.rb
+++ b/spec/rubocop/cop/migration/update_column_in_batches_spec.rb
@@ -54,8 +54,8 @@ describe RuboCop::Cop::Migration::UpdateColumnInBatches do
       aggregate_failures do
         expect(cop.offenses.size).to eq(1)
         expect(cop.offenses.map(&:line)).to eq([2])
-        expect(cop.offenses.first.message).
-          to include("`#{relative_spec_filepath}`")
+        expect(cop.offenses.first.message)
+          .to include("`#{relative_spec_filepath}`")
       end
     end
   end
diff --git a/spec/rubocop/cop/project_path_helper_spec.rb b/spec/rubocop/cop/project_path_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..bc47b45cad71048214b3cb4ca25e55ed6886e54b
--- /dev/null
+++ b/spec/rubocop/cop/project_path_helper_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+require 'rubocop'
+require 'rubocop/rspec/support'
+
+require_relative '../../../rubocop/cop/project_path_helper'
+
+describe RuboCop::Cop::ProjectPathHelper do
+  include CopHelper
+
+  subject(:cop) { described_class.new }
+
+  context "when using namespace_project with the project's namespace" do
+    let(:source) { 'edit_namespace_project_issue_path(@issue.project.namespace, @issue.project, @issue)' }
+    let(:correct_source) { 'edit_project_issue_path(@issue.project, @issue)' }
+
+    it 'registers an offense' do
+      inspect_source(cop, source)
+
+      aggregate_failures do
+        expect(cop.offenses.size).to eq(1)
+        expect(cop.offenses.map(&:line)).to eq([1])
+        expect(cop.highlights).to eq(['edit_namespace_project_issue_path'])
+      end
+    end
+
+    it 'autocorrects to the right version' do
+      autocorrected = autocorrect_source(cop, source)
+
+      expect(autocorrected).to eq(correct_source)
+    end
+  end
+
+  context 'when using namespace_project with a different namespace' do
+    it 'registers no offense' do
+      inspect_source(cop, 'edit_namespace_project_issue_path(namespace, project)')
+
+      expect(cop.offenses.size).to eq(0)
+    end
+  end
+end
diff --git a/spec/serializers/deploy_key_entity_spec.rb b/spec/serializers/deploy_key_entity_spec.rb
index ed89fccc3d0ab9e7d51e747b12ae1f330eb76457..9620f9665cf115b5be61156fecc2b95233b1ffcb 100644
--- a/spec/serializers/deploy_key_entity_spec.rb
+++ b/spec/serializers/deploy_key_entity_spec.rb
@@ -29,7 +29,7 @@ describe DeployKeyEntity do
           {
             id: project.id,
             name: project.name,
-            full_path: namespace_project_path(project.namespace, project),
+            full_path: project_path(project),
             full_name: project.full_name
           }
         ]
diff --git a/spec/services/access_token_validation_service_spec.rb b/spec/services/access_token_validation_service_spec.rb
index 87f093ee8ce9183a0e0434017bda56fdbe1d6559..11225fad18a44e1fda058783ca52a669d1988a45 100644
--- a/spec/services/access_token_validation_service_spec.rb
+++ b/spec/services/access_token_validation_service_spec.rb
@@ -2,40 +2,71 @@ require 'spec_helper'
 
 describe AccessTokenValidationService, services: true do
   describe ".include_any_scope?" do
+    let(:request) { double("request") }
+
     it "returns true if the required scope is present in the token's scopes" do
       token = double("token", scopes: [:api, :read_user])
+      scopes = [:api]
 
-      expect(described_class.new(token).include_any_scope?([:api])).to be(true)
+      expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(true)
     end
 
     it "returns true if more than one of the required scopes is present in the token's scopes" do
       token = double("token", scopes: [:api, :read_user, :other_scope])
+      scopes = [:api, :other_scope]
 
-      expect(described_class.new(token).include_any_scope?([:api, :other_scope])).to be(true)
+      expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(true)
     end
 
     it "returns true if the list of required scopes is an exact match for the token's scopes" do
       token = double("token", scopes: [:api, :read_user, :other_scope])
+      scopes = [:api, :read_user, :other_scope]
 
-      expect(described_class.new(token).include_any_scope?([:api, :read_user, :other_scope])).to be(true)
+      expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(true)
     end
 
     it "returns true if the list of required scopes contains all of the token's scopes, in addition to others" do
       token = double("token", scopes: [:api, :read_user])
+      scopes = [:api, :read_user, :other_scope]
 
-      expect(described_class.new(token).include_any_scope?([:api, :read_user, :other_scope])).to be(true)
+      expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(true)
     end
 
     it 'returns true if the list of required scopes is blank' do
       token = double("token", scopes: [])
+      scopes = []
 
-      expect(described_class.new(token).include_any_scope?([])).to be(true)
+      expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(true)
     end
 
     it "returns false if there are no scopes in common between the required scopes and the token scopes" do
       token = double("token", scopes: [:api, :read_user])
+      scopes = [:other_scope]
+
+      expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(false)
+    end
+
+    context "conditions" do
+      it "ignores any scopes whose `if` condition returns false" do
+        token = double("token", scopes: [:api, :read_user])
+        scopes = [API::Scope.new(:api, if: ->(_) { false })]
+
+        expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(false)
+      end
+
+      it "does not ignore scopes whose `if` condition is not set" do
+        token = double("token", scopes: [:api, :read_user])
+        scopes = [API::Scope.new(:api, if: ->(_) { false }), :read_user]
+
+        expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(true)
+      end
+
+      it "does not ignore scopes whose `if` condition returns true" do
+        token = double("token", scopes: [:api, :read_user])
+        scopes = [API::Scope.new(:api, if: ->(_) { true }), API::Scope.new(:read_user, if: ->(_) { false })]
 
-      expect(described_class.new(token).include_any_scope?([:other_scope])).to be(false)
+        expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(true)
+      end
     end
   end
 end
diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb
index a1e220c2322dff734a9208ce7b779d78ec688389..a66cc2cd6e97fa3c91c801b9530906bc2e205b91 100644
--- a/spec/services/boards/issues/list_service_spec.rb
+++ b/spec/services/boards/issues/list_service_spec.rb
@@ -67,7 +67,7 @@ describe Boards::Issues::ListService, services: true do
 
         issues = described_class.new(project, user, params).execute
 
-        expect(issues).to eq [closed_issue4, closed_issue2, closed_issue3, closed_issue1]
+        expect(issues).to eq [closed_issue4, closed_issue2, closed_issue5, closed_issue3, closed_issue1]
       end
 
       it 'returns opened issues that have label list applied when listing issues from a label list' do
diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb
index 1557cb3c9381dd96dcd3cb2e536b45aa69527e71..efcaccc254e1e8a525e00e4ae596e550bb9bb8d9 100644
--- a/spec/services/ci/process_pipeline_service_spec.rb
+++ b/spec/services/ci/process_pipeline_service_spec.rb
@@ -62,6 +62,10 @@ describe Ci::ProcessPipelineService, '#execute', :services do
       fail_running_or_pending
 
       expect(builds_statuses).to eq %w(failed pending)
+
+      fail_running_or_pending
+
+      expect(pipeline.reload).to be_success
     end
   end
 
diff --git a/spec/services/create_deployment_service_spec.rb b/spec/services/create_deployment_service_spec.rb
index 6cf4342ad4c08964eaf9011337b5c356787f8eac..dfab6ebf372a8a9d86c4dd8825ebba07f0983f48 100644
--- a/spec/services/create_deployment_service_spec.rb
+++ b/spec/services/create_deployment_service_spec.rb
@@ -122,6 +122,61 @@ describe CreateDeploymentService, services: true do
     end
   end
 
+  describe '#expanded_environment_url' do
+    subject { service.send(:expanded_environment_url) }
+
+    context 'when yaml environment uses $CI_COMMIT_REF_NAME' do
+      let(:job) do
+        create(:ci_build,
+               ref: 'master',
+               options: { environment: { url: 'http://review/$CI_COMMIT_REF_NAME' } })
+      end
+
+      it { is_expected.to eq('http://review/master') }
+    end
+
+    context 'when yaml environment uses $CI_ENVIRONMENT_SLUG' do
+      let(:job) do
+        create(:ci_build,
+               ref: 'master',
+               environment: 'production',
+               options: { environment: { url: 'http://review/$CI_ENVIRONMENT_SLUG' } })
+      end
+
+      let!(:environment) do
+        create(:environment,
+          project: job.project,
+          name: 'production',
+          slug: 'prod-slug',
+          external_url: 'http://review/old')
+      end
+
+      it { is_expected.to eq('http://review/prod-slug') }
+    end
+
+    context 'when yaml environment uses yaml_variables containing symbol keys' do
+      let(:job) do
+        create(:ci_build,
+               yaml_variables: [{ key: :APP_HOST, value: 'host' }],
+               options: { environment: { url: 'http://review/$APP_HOST' } })
+      end
+
+      it { is_expected.to eq('http://review/host') }
+    end
+
+    context 'when yaml environment does not have url' do
+      let(:job) { create(:ci_build, environment: 'staging') }
+
+      let!(:environment) do
+        create(:environment, project: job.project, name: job.environment)
+      end
+
+      it 'returns the external_url from persisted environment' do
+        is_expected.to be_nil
+      end
+    end
+  end
+
   describe 'processing of builds' do
     shared_examples 'does not create deployment' do
       it 'does not create a new deployment' do
diff --git a/spec/services/delete_merged_branches_service_spec.rb b/spec/services/delete_merged_branches_service_spec.rb
index cae74df9c90afe05a21e87486a3e4b067dd00df4..fe21ca0b3cbc82f3b090dc472ea5077a7c73704b 100644
--- a/spec/services/delete_merged_branches_service_spec.rb
+++ b/spec/services/delete_merged_branches_service_spec.rb
@@ -24,6 +24,14 @@ describe DeleteMergedBranchesService, services: true do
       expect(project.repository.branch_names).to include('master')
     end
 
+    it 'keeps protected branches' do
+      create(:protected_branch, project: project, name: 'improve/awesome')
+
+      service.execute
+
+      expect(project.repository.branch_names).to include('improve/awesome')
+    end
+
     context 'user without rights' do
       let(:user) { create(:user) }
 
diff --git a/spec/services/emails/create_service_spec.rb b/spec/services/emails/create_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c1f477f551eef8c95dbc66f588f4f00e384a32e6
--- /dev/null
+++ b/spec/services/emails/create_service_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe Emails::CreateService, services: true do
+  let(:user) { create(:user) }
+  let(:opts) { { email: 'new@email.com' } }
+
+  subject(:service) { described_class.new(user, opts) }
+
+  describe '#execute' do
+    it 'creates an email with valid attributes' do
+      expect { service.execute }.to change { Email.count }.by(1)
+      expect(Email.where(opts)).not_to be_empty
+    end
+
+    it 'has the right user association' do
+      service.execute
+
+      expect(user.emails).to eq(Email.where(opts))
+    end
+  end
+end
diff --git a/spec/services/emails/destroy_service_spec.rb b/spec/services/emails/destroy_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5e7ab4a40af868345961738f467415bc510c4601
--- /dev/null
+++ b/spec/services/emails/destroy_service_spec.rb
@@ -0,0 +1,14 @@
+require 'spec_helper'
+
+describe Emails::DestroyService, services: true do
+  let!(:user) { create(:user) }
+  let!(:email) { create(:email, user: user) }
+
+  subject(:service) { described_class.new(user, email: email.email) }
+
+  describe '#execute' do
+    it 'removes an email' do
+      expect { service.execute }.to change { user.emails.count }.by(-1)
+    end
+  end
+end
diff --git a/spec/services/files/update_service_spec.rb b/spec/services/files/update_service_spec.rb
index 16bca66766ac952058ccec4b4dbe28eebef64e48..cc950ae6bb3a7a0fab0ab7cf19724d50115b4e8d 100644
--- a/spec/services/files/update_service_spec.rb
+++ b/spec/services/files/update_service_spec.rb
@@ -32,8 +32,8 @@ describe Files::UpdateService do
       let(:last_commit_sha) { "foo" }
 
       it "returns a hash with the correct error message and a :error status " do
-        expect { subject.execute }.
-          to raise_error(Files::UpdateService::FileChangedError,
+        expect { subject.execute }
+          .to raise_error(Files::UpdateService::FileChangedError,
                          "You are attempting to update a file that has changed since you started editing it.")
       end
     end
diff --git a/spec/services/git_hooks_service_spec.rb b/spec/services/git_hooks_service_spec.rb
index ac7ccfbaab01dfd482e7c6d3e6de5b209b6fd3bd..213678c27f5b73b6b8869da07ff33c6efec9864a 100644
--- a/spec/services/git_hooks_service_spec.rb
+++ b/spec/services/git_hooks_service_spec.rb
@@ -12,7 +12,6 @@ describe GitHooksService, services: true do
     @oldrev = sample_commit.parent_id
     @newrev = sample_commit.id
     @ref = 'refs/heads/feature'
-    @repo_path = project.repository.path_to_repo
   end
 
   describe '#execute' do
@@ -21,7 +20,7 @@ describe GitHooksService, services: true do
         hook = double(trigger: [true, nil])
         expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook)
 
-        service.execute(user, @repo_path, @blankrev, @newrev, @ref) { }
+        service.execute(user, project, @blankrev, @newrev, @ref) { }
       end
     end
 
@@ -31,7 +30,7 @@ describe GitHooksService, services: true do
         expect(service).not_to receive(:run_hook).with('post-receive')
 
         expect do
-          service.execute(user, @repo_path, @blankrev, @newrev, @ref)
+          service.execute(user, project, @blankrev, @newrev, @ref)
         end.to raise_error(GitHooksService::PreReceiveError)
       end
     end
@@ -43,7 +42,7 @@ describe GitHooksService, services: true do
         expect(service).not_to receive(:run_hook).with('post-receive')
 
         expect do
-          service.execute(user, @repo_path, @blankrev, @newrev, @ref)
+          service.execute(user, project, @blankrev, @newrev, @ref)
         end.to raise_error(GitHooksService::PreReceiveError)
       end
     end
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index bcd1fb64ab973256d996f9e11e94cab206c442bf..8e8816870e1a30919233738eac30bfd5dbde6538 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -158,8 +158,8 @@ describe GitPushService, services: true do
 
     context "Updates merge requests" do
       it "when pushing a new branch for the first time" do
-        expect(UpdateMergeRequestsWorker).to receive(:perform_async).
-                                                with(project.id, user.id, @blankrev, 'newrev', 'refs/heads/master')
+        expect(UpdateMergeRequestsWorker).to receive(:perform_async)
+                                                .with(project.id, user.id, @blankrev, 'newrev', 'refs/heads/master')
         execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' )
       end
     end
@@ -283,8 +283,8 @@ describe GitPushService, services: true do
         author_email: commit_author.email
       )
 
-      allow_any_instance_of(ProcessCommitWorker).to receive(:build_commit).
-        and_return(commit)
+      allow_any_instance_of(ProcessCommitWorker).to receive(:build_commit)
+        .and_return(commit)
 
       allow(project.repository).to receive(:commits_between).and_return([commit])
     end
@@ -341,8 +341,8 @@ describe GitPushService, services: true do
         committed_date: commit_time
       )
 
-      allow_any_instance_of(ProcessCommitWorker).to receive(:build_commit).
-        and_return(commit)
+      allow_any_instance_of(ProcessCommitWorker).to receive(:build_commit)
+        .and_return(commit)
 
       allow(project.repository).to receive(:commits_between).and_return([commit])
     end
@@ -377,11 +377,11 @@ describe GitPushService, services: true do
         author_email: commit_author.email
       )
 
-      allow(project.repository).to receive(:commits_between).
-        and_return([closing_commit])
+      allow(project.repository).to receive(:commits_between)
+        .and_return([closing_commit])
 
-      allow_any_instance_of(ProcessCommitWorker).to receive(:build_commit).
-        and_return(closing_commit)
+      allow_any_instance_of(ProcessCommitWorker).to receive(:build_commit)
+        .and_return(closing_commit)
 
       project.team << [commit_author, :master]
     end
@@ -401,18 +401,6 @@ describe GitPushService, services: true do
         expect(SystemNoteService).not_to receive(:cross_reference)
         execute_service(project, commit_author, @oldrev, @newrev, @ref )
       end
-
-      it "doesn't close issues when external issue tracker is in use" do
-        allow_any_instance_of(Project).to receive(:default_issues_tracker?).
-          and_return(false)
-        external_issue_tracker = double(title: 'My Tracker', issue_path: issue.iid, reference_pattern: project.issue_reference_pattern)
-        allow_any_instance_of(Project).to receive(:external_issue_tracker).and_return(external_issue_tracker)
-
-        # The push still shouldn't create cross-reference notes.
-        expect do
-          execute_service(project, commit_author, @oldrev, @newrev,  'refs/heads/hurf' )
-        end.not_to change { Note.where(project_id: project.id, system: true).count }
-      end
     end
 
     context "to non-default branches" do
@@ -598,13 +586,13 @@ describe GitPushService, services: true do
         commit = double(:commit)
         diff = double(:diff, new_path: 'README.md')
 
-        expect(commit).to receive(:raw_deltas).
-          and_return([diff])
+        expect(commit).to receive(:raw_deltas)
+          .and_return([diff])
 
         service.push_commits = [commit]
 
-        expect(ProjectCacheWorker).to receive(:perform_async).
-          with(project.id, %i(readme), %i(commit_count repository_size))
+        expect(ProjectCacheWorker).to receive(:perform_async)
+          .with(project.id, %i(readme), %i(commit_count repository_size))
 
         service.update_caches
       end
@@ -616,9 +604,9 @@ describe GitPushService, services: true do
       end
 
       it 'does not flush any conditional caches' do
-        expect(ProjectCacheWorker).to receive(:perform_async).
-          with(project.id, [], %i(commit_count repository_size)).
-          and_call_original
+        expect(ProjectCacheWorker).to receive(:perform_async)
+          .with(project.id, [], %i(commit_count repository_size))
+          .and_call_original
 
         service.update_caches
       end
@@ -635,8 +623,8 @@ describe GitPushService, services: true do
     end
 
     it 'only schedules a limited number of commits' do
-      allow(service).to receive(:push_commits).
-        and_return(Array.new(1000, double(:commit, to_hash: {}, matches_cross_reference_regex?: true)))
+      allow(service).to receive(:push_commits)
+        .and_return(Array.new(1000, double(:commit, to_hash: {}, matches_cross_reference_regex?: true)))
 
       expect(ProcessCommitWorker).to receive(:perform_async).exactly(100).times
 
@@ -644,8 +632,8 @@ describe GitPushService, services: true do
     end
 
     it "skips commits which don't include cross-references" do
-      allow(service).to receive(:push_commits).
-        and_return([double(:commit, to_hash: {}, matches_cross_reference_regex?: false)])
+      allow(service).to receive(:push_commits)
+        .and_return([double(:commit, to_hash: {}, matches_cross_reference_regex?: false)])
 
       expect(ProcessCommitWorker).not_to receive(:perform_async)
 
diff --git a/spec/services/groups/destroy_service_spec.rb b/spec/services/groups/destroy_service_spec.rb
index a37257d1bf4846abe08f7eeb055ff31b353c3c31..d59b37bee360ceff8e40ca2c952136021d2d8823 100644
--- a/spec/services/groups/destroy_service_spec.rb
+++ b/spec/services/groups/destroy_service_spec.rb
@@ -15,6 +15,14 @@ describe Groups::DestroyService, services: true do
     group.add_user(user, Gitlab::Access::OWNER)
   end
 
+  def destroy_group(group, user, async)
+    if async
+      Groups::DestroyService.new(group, user).async_execute
+    else
+      Groups::DestroyService.new(group, user).execute
+    end
+  end
+
   shared_examples 'group destruction' do |async|
     context 'database records' do
       before do
@@ -30,30 +38,14 @@ describe Groups::DestroyService, services: true do
     context 'file system' do
       context 'Sidekiq inline' do
         before do
-          # Run sidekiq immediatly to check that renamed dir will be removed
+          # Run sidekiq immediately to check that renamed dir will be removed
           Sidekiq::Testing.inline! { destroy_group(group, user, async) }
         end
 
-        it { expect(gitlab_shell.exists?(project.repository_storage_path, group.path)).to be_falsey }
-        it { expect(gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_falsey }
-      end
-
-      context 'Sidekiq fake' do
-        before do
-          # Don't run sidekiq to check if renamed repository exists
-          Sidekiq::Testing.fake! { destroy_group(group, user, async) }
+        it 'verifies that paths have been deleted' do
+          expect(gitlab_shell.exists?(project.repository_storage_path, group.path)).to be_falsey
+          expect(gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_falsey
         end
-
-        it { expect(gitlab_shell.exists?(project.repository_storage_path, group.path)).to be_falsey }
-        it { expect(gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_truthy }
-      end
-    end
-
-    def destroy_group(group, user, async)
-      if async
-        Groups::DestroyService.new(group, user).async_execute
-      else
-        Groups::DestroyService.new(group, user).execute
       end
     end
   end
@@ -61,6 +53,26 @@ describe Groups::DestroyService, services: true do
   describe 'asynchronous delete' do
     it_behaves_like 'group destruction', true
 
+    context 'Sidekiq fake' do
+      before do
+        # Don't run Sidekiq to verify that group and projects are not actually destroyed
+        Sidekiq::Testing.fake! { destroy_group(group, user, true) }
+      end
+
+      after do
+        # Clean up stale directories
+        gitlab_shell.rm_namespace(project.repository_storage_path, group.path)
+        gitlab_shell.rm_namespace(project.repository_storage_path, remove_path)
+      end
+
+      it 'verifies original paths and projects still exist' do
+        expect(gitlab_shell.exists?(project.repository_storage_path, group.path)).to be_truthy
+        expect(gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_falsey
+        expect(Project.unscoped.count).to eq(1)
+        expect(Group.unscoped.count).to eq(2)
+      end
+    end
+
     context 'potential race conditions' do
       context "when the `GroupDestroyWorker` task runs immediately" do
         it "deletes the group" do
diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb
index be0e829880e1a28886f2dca3ad66992ef204ceb1..d6f4c69406979c284a135907fcb873fc107f0d18 100644
--- a/spec/services/issues/close_service_spec.rb
+++ b/spec/services/issues/close_service_spec.rb
@@ -18,26 +18,26 @@ describe Issues::CloseService, services: true do
     let(:service) { described_class.new(project, user) }
 
     it 'checks if the user is authorized to update the issue' do
-      expect(service).to receive(:can?).with(user, :update_issue, issue).
-        and_call_original
+      expect(service).to receive(:can?).with(user, :update_issue, issue)
+        .and_call_original
 
       service.execute(issue)
     end
 
     it 'does not close the issue when the user is not authorized to do so' do
-      allow(service).to receive(:can?).with(user, :update_issue, issue).
-        and_return(false)
+      allow(service).to receive(:can?).with(user, :update_issue, issue)
+        .and_return(false)
 
       expect(service).not_to receive(:close_issue)
       expect(service.execute(issue)).to eq(issue)
     end
 
     it 'closes the issue when the user is authorized to do so' do
-      allow(service).to receive(:can?).with(user, :update_issue, issue).
-        and_return(true)
+      allow(service).to receive(:can?).with(user, :update_issue, issue)
+        .and_return(true)
 
-      expect(service).to receive(:close_issue).
-        with(issue, commit: nil, notifications: true, system_note: true)
+      expect(service).to receive(:close_issue)
+        .with(issue, commit: nil, notifications: true, system_note: true)
 
       service.execute(issue)
     end
diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb
index 370bd35220005fb91c3fed7a12f344190bfe99c7..ae9d2b2855d0aa0ba3731acce4107c4c1cc5ecdf 100644
--- a/spec/services/issues/create_service_spec.rb
+++ b/spec/services/issues/create_service_spec.rb
@@ -206,9 +206,9 @@ describe Issues::CreateService, services: true do
       end
     end
 
-    it_behaves_like 'new issuable record that supports slash commands'
+    it_behaves_like 'new issuable record that supports quick actions'
 
-    context 'Slash commands' do
+    context 'Quick actions' do
       context 'with assignee and milestone in params and command' do
         let(:opts) do
           {
diff --git a/spec/services/labels/promote_service_spec.rb b/spec/services/labels/promote_service_spec.rb
index 4b90ad19640cf07eb95540eb69b4e172fe950f92..500afdfb9161bd5e5178e4c56e0b1fe8b724c4c8 100644
--- a/spec/services/labels/promote_service_spec.rb
+++ b/spec/services/labels/promote_service_spec.rb
@@ -66,9 +66,9 @@ describe Labels::PromoteService, services: true do
       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 { 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
 
diff --git a/spec/services/members/destroy_service_spec.rb b/spec/services/members/destroy_service_spec.rb
index 41450c67d7e77c1798e3a46cbcc2405b104be838..9ab7839430c6e86197621ade062e019bab1a6adb 100644
--- a/spec/services/members/destroy_service_spec.rb
+++ b/spec/services/members/destroy_service_spec.rb
@@ -104,8 +104,8 @@ describe Members::DestroyService, services: true do
         let(:params) { { id: project.members.find_by!(user_id: user.id).id } }
 
         it 'destroys the member' do
-          expect { described_class.new(project, user, params).execute }.
-            to change { project.members.count }.by(-1)
+          expect { described_class.new(project, user, params).execute }
+            .to change { project.members.count }.by(-1)
         end
       end
     end
diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb
index 154f30aac3b4ab2ed2878eebf885553e9cf9aa15..074d4672b06cbbdc39ca5d1055bdbcb47f1e20e4 100644
--- a/spec/services/merge_requests/close_service_spec.rb
+++ b/spec/services/merge_requests/close_service_spec.rb
@@ -32,8 +32,8 @@ describe MergeRequests::CloseService, services: true do
       it { expect(@merge_request).to be_closed }
 
       it 'executes hooks with close action' do
-        expect(service).to have_received(:execute_hooks).
-                               with(@merge_request, 'close')
+        expect(service).to have_received(:execute_hooks)
+                               .with(@merge_request, 'close')
       end
 
       it 'sends email to user2 about assign of new merge_request' do
diff --git a/spec/services/merge_requests/conflicts/resolve_service_spec.rb b/spec/services/merge_requests/conflicts/resolve_service_spec.rb
index c77e6e9cd50dd51ae984be2ed275c32b5b707968..6f49a65d795af795de962b85bfae4b51a238da73 100644
--- a/spec/services/merge_requests/conflicts/resolve_service_spec.rb
+++ b/spec/services/merge_requests/conflicts/resolve_service_spec.rb
@@ -64,9 +64,9 @@ describe MergeRequests::Conflicts::ResolveService do
         end
 
         it 'creates a commit with the correct parents' do
-          expect(merge_request.source_branch_head.parents.map(&:id)).
-            to eq(%w(1450cd639e0bc6721eb02800169e464f212cde06
-                     824be604a34828eb682305f0d963056cfac87b2d))
+          expect(merge_request.source_branch_head.parents.map(&:id))
+            .to eq(%w(1450cd639e0bc6721eb02800169e464f212cde06
+                      824be604a34828eb682305f0d963056cfac87b2d))
         end
       end
 
@@ -129,9 +129,8 @@ describe MergeRequests::Conflicts::ResolveService do
         it 'creates a commit with the correct parents' do
           resolve_conflicts
 
-          expect(merge_request_from_fork.source_branch_head.parents.map(&:id)).
-            to eq(['404fa3fc7c2c9b5dacff102f353bdf55b1be2813',
-                   target_head])
+          expect(merge_request_from_fork.source_branch_head.parents.map(&:id))
+            .to eq(['404fa3fc7c2c9b5dacff102f353bdf55b1be2813', target_head])
         end
       end
     end
@@ -169,9 +168,9 @@ describe MergeRequests::Conflicts::ResolveService do
       end
 
       it 'creates a commit with the correct parents' do
-        expect(merge_request.source_branch_head.parents.map(&:id)).
-          to eq(%w(1450cd639e0bc6721eb02800169e464f212cde06
-                   824be604a34828eb682305f0d963056cfac87b2d))
+        expect(merge_request.source_branch_head.parents.map(&:id))
+          .to eq(%w(1450cd639e0bc6721eb02800169e464f212cde06
+                    824be604a34828eb682305f0d963056cfac87b2d))
       end
 
       it 'sets the content to the content given' do
@@ -204,8 +203,8 @@ describe MergeRequests::Conflicts::ResolveService do
       end
 
       it 'raises a MissingResolution error' do
-        expect { service.execute(user, invalid_params) }.
-          to raise_error(Gitlab::Conflict::File::MissingResolution)
+        expect { service.execute(user, invalid_params) }
+          .to raise_error(Gitlab::Conflict::File::MissingResolution)
       end
     end
 
@@ -230,8 +229,8 @@ describe MergeRequests::Conflicts::ResolveService do
       end
 
       it 'raises a MissingResolution error' do
-        expect { service.execute(user, invalid_params) }.
-          to raise_error(Gitlab::Conflict::File::MissingResolution)
+        expect { service.execute(user, invalid_params) }
+          .to raise_error(Gitlab::Conflict::File::MissingResolution)
       end
     end
 
@@ -250,8 +249,8 @@ describe MergeRequests::Conflicts::ResolveService do
       end
 
       it 'raises a MissingFiles error' do
-        expect { service.execute(user, invalid_params) }.
-          to raise_error(described_class::MissingFiles)
+        expect { service.execute(user, invalid_params) }
+          .to raise_error(described_class::MissingFiles)
       end
     end
   end
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index 13fee953e41c873102e073485d41b5711511d098..36a2b6724737aa80f2588ac8394d627b8359a6a7 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -83,9 +83,9 @@ describe MergeRequests::CreateService, services: true do
         let!(:pipeline_3) { create(:ci_pipeline, project: project, ref: "other_branch", project_id: project.id) }
 
         before do
-          project.merge_requests.
-            where(source_branch: opts[:source_branch], target_branch: opts[:target_branch]).
-            destroy_all
+          project.merge_requests
+            .where(source_branch: opts[:source_branch], target_branch: opts[:target_branch])
+            .destroy_all
         end
 
         it 'sets head pipeline' do
@@ -108,7 +108,7 @@ describe MergeRequests::CreateService, services: true do
       end
     end
 
-    it_behaves_like 'new issuable record that supports slash commands' do
+    it_behaves_like 'new issuable record that supports quick actions' do
       let(:default_params) do
         {
           source_branch: 'feature',
@@ -117,7 +117,7 @@ describe MergeRequests::CreateService, services: true do
       end
     end
 
-    context 'Slash commands' do
+    context 'Quick actions' do
       context 'with assignee and milestone in params and command' do
         let(:merge_request) { described_class.new(project, user, opts).execute }
         let(:milestone) { create(:milestone, project: project) }
diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb
index b3b188a805f5e45e2cf66322cfb09a7016243948..19d9e4049fe7cc1e3200db12341369a71f707eb8 100644
--- a/spec/services/merge_requests/merge_service_spec.rb
+++ b/spec/services/merge_requests/merge_service_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe MergeRequests::MergeService, services: true do
   let(:user) { create(:user) }
   let(:user2) { create(:user) }
-  let(:merge_request) { create(:merge_request, assignee: user2) }
+  let(:merge_request) { create(:merge_request, :simple, author: user2, assignee: user2) }
   let(:project) { merge_request.project }
 
   before do
@@ -133,18 +133,65 @@ describe MergeRequests::MergeService, services: true do
       it { expect(todo).to be_done }
     end
 
-    context 'remove source branch by author' do
-      let(:service) do
-        merge_request.merge_params['force_remove_source_branch'] = '1'
-        merge_request.save!
-        MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message')
+    context 'source branch removal' do
+      context 'when the source branch is protected' do
+        let(:service) do
+          MergeRequests::MergeService.new(project, user, should_remove_source_branch: '1')
+        end
+
+        before do
+          create(:protected_branch, project: project, name: merge_request.source_branch)
+        end
+
+        it 'does not delete the source branch' do
+          expect(DeleteBranchService).not_to receive(:new)
+          service.execute(merge_request)
+        end
       end
 
-      it 'removes the source branch' do
-        expect(DeleteBranchService).to receive(:new).
-          with(merge_request.source_project, merge_request.author).
-          and_call_original
-        service.execute(merge_request)
+      context 'when the source branch is the default branch' do
+        let(:service) do
+          MergeRequests::MergeService.new(project, user, should_remove_source_branch: '1')
+        end
+
+        before do
+          allow(project).to receive(:root_ref?).with(merge_request.source_branch).and_return(true)
+        end
+
+        it 'does not delete the source branch' do
+          expect(DeleteBranchService).not_to receive(:new)
+          service.execute(merge_request)
+        end
+      end
+
+      context 'when the source branch can be removed' do
+        context 'when MR author set the source branch to be removed' do
+          let(:service) do
+            merge_request.merge_params['force_remove_source_branch'] = '1'
+            merge_request.save!
+            MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message')
+          end
+
+          it 'removes the source branch using the author user' do
+            expect(DeleteBranchService).to receive(:new)
+              .with(merge_request.source_project, merge_request.author)
+              .and_call_original
+            service.execute(merge_request)
+          end
+        end
+
+        context 'when MR merger set the source branch to be removed' do
+          let(:service) do
+            MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message', should_remove_source_branch: '1')
+          end
+
+          it 'removes the source branch using the current user' do
+            expect(DeleteBranchService).to receive(:new)
+              .with(merge_request.source_project, user)
+              .and_call_original
+            service.execute(merge_request)
+          end
+        end
       end
     end
 
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 1f109eab268807b1400ea6402d4cc55dfa22a47b..671a932441e433347bf23609b193be1e1661afd1 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -57,8 +57,8 @@ describe MergeRequests::RefreshService, services: true do
       end
 
       it 'executes hooks with update action' do
-        expect(refresh_service).to have_received(:execute_hooks).
-          with(@merge_request, 'update', @oldrev)
+        expect(refresh_service).to have_received(:execute_hooks)
+          .with(@merge_request, 'update', @oldrev)
 
         expect(@merge_request.notes).not_to be_empty
         expect(@merge_request).to be_open
@@ -83,8 +83,8 @@ describe MergeRequests::RefreshService, services: true do
       end
 
       it 'executes hooks with update action' do
-        expect(refresh_service).to have_received(:execute_hooks).
-          with(@merge_request, 'update', @oldrev)
+        expect(refresh_service).to have_received(:execute_hooks)
+          .with(@merge_request, 'update', @oldrev)
 
         expect(@merge_request.notes).not_to be_empty
         expect(@merge_request).to be_open
@@ -146,8 +146,8 @@ describe MergeRequests::RefreshService, services: true do
         end
 
         it 'executes hooks with update action' do
-          expect(refresh_service).to have_received(:execute_hooks).
-            with(@fork_merge_request, 'update', @oldrev)
+          expect(refresh_service).to have_received(:execute_hooks)
+            .with(@fork_merge_request, 'update', @oldrev)
 
           expect(@merge_request.notes).to be_empty
           expect(@merge_request).to be_open
@@ -228,8 +228,8 @@ describe MergeRequests::RefreshService, services: true do
       let(:refresh_service) { service.new(@fork_project, @user) }
 
       it 'refreshes the merge request' do
-        expect(refresh_service).to receive(:execute_hooks).
-                                       with(@fork_merge_request, 'update', Gitlab::Git::BLANK_SHA)
+        expect(refresh_service).to receive(:execute_hooks)
+                                       .with(@fork_merge_request, 'update', Gitlab::Git::BLANK_SHA)
         allow_any_instance_of(Repository).to receive(:merge_base).and_return(@oldrev)
 
         refresh_service.execute(Gitlab::Git::BLANK_SHA, @newrev, 'refs/heads/master')
diff --git a/spec/services/merge_requests/reopen_service_spec.rb b/spec/services/merge_requests/reopen_service_spec.rb
index b6d4db2f922e638e65b49a26966e7e0b5cd799cd..6cc403bdb7fba068c1ae737ea17273d74c120433 100644
--- a/spec/services/merge_requests/reopen_service_spec.rb
+++ b/spec/services/merge_requests/reopen_service_spec.rb
@@ -31,8 +31,8 @@ describe MergeRequests::ReopenService, services: true do
       it { expect(merge_request).to be_reopened }
 
       it 'executes hooks with reopen action' do
-        expect(service).to have_received(:execute_hooks).
-                               with(merge_request, 'reopen')
+        expect(service).to have_received(:execute_hooks)
+                               .with(merge_request, 'reopen')
       end
 
       it 'sends email to user2 about reopen of merge_request' do
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index fd46020bbdb7b922747f3a3e8829ed1c3bc2945a..ec15b5cac14a15a93b69f834ffcaf4cc1e565ec3 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -78,8 +78,8 @@ describe MergeRequests::UpdateService, services: true do
       end
 
       it 'executes hooks with update action' do
-        expect(service).to have_received(:execute_hooks).
-                               with(@merge_request, 'update')
+        expect(service).to have_received(:execute_hooks)
+                               .with(@merge_request, 'update')
       end
 
       it 'sends email to user2 about assign of new merge request and email to user3 about merge request unassignment' do
@@ -195,8 +195,8 @@ describe MergeRequests::UpdateService, services: true do
             head_pipeline_of: merge_request
           )
 
-          expect(MergeRequests::MergeWhenPipelineSucceedsService).to receive(:new).with(project, user).
-            and_return(service_mock)
+          expect(MergeRequests::MergeWhenPipelineSucceedsService).to receive(:new).with(project, user)
+            .and_return(service_mock)
           expect(service_mock).to receive(:execute).with(merge_request)
         end
 
diff --git a/spec/services/notes/slash_commands_service_spec.rb b/spec/services/notes/quick_actions_service_spec.rb
similarity index 94%
rename from spec/services/notes/slash_commands_service_spec.rb
rename to spec/services/notes/quick_actions_service_spec.rb
index d5ffc1908a90de996df030b8ad4984846ff58bf0..9a98499826f863f8cc4672c937862f81363ccb70 100644
--- a/spec/services/notes/slash_commands_service_spec.rb
+++ b/spec/services/notes/quick_actions_service_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Notes::SlashCommandsService, services: true do
+describe Notes::QuickActionsService, services: true do
   shared_context 'note on noteable' do
     let(:project) { create(:empty_project) }
     let(:master) { create(:user).tap { |u| project.team << [u, :master] } }
@@ -11,7 +11,7 @@ describe Notes::SlashCommandsService, services: true do
     end
   end
 
-  shared_examples 'note on noteable that does not support slash commands' do
+  shared_examples 'note on noteable that does not support quick actions' do
     include_context 'note on noteable'
 
     before do
@@ -45,7 +45,7 @@ describe Notes::SlashCommandsService, services: true do
     end
   end
 
-  shared_examples 'note on noteable that supports slash commands' do
+  shared_examples 'note on noteable that supports quick actions' do
     include_context 'note on noteable'
 
     before do
@@ -210,15 +210,15 @@ describe Notes::SlashCommandsService, services: true do
   describe '#execute' do
     let(:service) { described_class.new(project, master) }
 
-    it_behaves_like 'note on noteable that supports slash commands' do
+    it_behaves_like 'note on noteable that supports quick actions' do
       let(:note) { build(:note_on_issue, project: project) }
     end
 
-    it_behaves_like 'note on noteable that supports slash commands' do
+    it_behaves_like 'note on noteable that supports quick actions' do
       let(:note) { build(:note_on_merge_request, project: project) }
     end
 
-    it_behaves_like 'note on noteable that does not support slash commands' do
+    it_behaves_like 'note on noteable that does not support quick actions' do
       let(:note) { build(:note_on_commit, project: project) }
     end
   end
diff --git a/spec/services/notification_recipient_service_spec.rb b/spec/services/notification_recipient_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..dfe1ee7c41e9fb01c9656f6818aa550c1e9d91d6
--- /dev/null
+++ b/spec/services/notification_recipient_service_spec.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+describe NotificationRecipientService, services: true do
+  set(:user) { create(:user) }
+  set(:project) { create(:empty_project, :public) }
+  set(:issue) { create(:issue, project: project) }
+
+  set(:watcher) do
+    watcher = create(:user)
+    setting = watcher.notification_settings_for(project)
+    setting.level = :watch
+    setting.save
+
+    watcher
+  end
+
+  subject { described_class.new(project) }
+
+  describe '#build_recipients' do
+    it 'does not modify the participants of the target' do
+      expect { subject.build_recipients(issue, user, action: :new_issue) }
+        .not_to change { issue.participants(user) }
+    end
+  end
+
+  describe '#build_new_note_recipients' do
+    set(:note) { create(:note_on_issue, noteable: issue, project: project) }
+
+    it 'does not modify the participants of the target' do
+      expect { subject.build_new_note_recipients(note) }
+        .not_to change { note.noteable.participants(note.author) }
+    end
+  end
+end
diff --git a/spec/services/preview_markdown_service_spec.rb b/spec/services/preview_markdown_service_spec.rb
index b2fb5c91313c084bd2f78560795d74f6fc812cf5..4fd9cb23ae18fa83d5cac524e9029205ca3f93b6 100644
--- a/spec/services/preview_markdown_service_spec.rb
+++ b/spec/services/preview_markdown_service_spec.rb
@@ -19,24 +19,24 @@ describe PreviewMarkdownService do
     end
   end
 
-  context 'new note with slash commands' do
+  context 'new note with quick actions' do
     let(:issue) { create(:issue, project: project) }
     let(:params) do
       {
         text: "Please do it\n/assign #{user.to_reference}",
-        slash_commands_target_type: 'Issue',
-        slash_commands_target_id: issue.id
+        quick_actions_target_type: 'Issue',
+        quick_actions_target_id: issue.id
       }
     end
     let(:service) { described_class.new(project, user, params) }
 
-    it 'removes slash commands from text' do
+    it 'removes quick actions from text' do
       result = service.execute
 
       expect(result[:text]).to eq 'Please do it'
     end
 
-    it 'explains slash commands effect' do
+    it 'explains quick actions effect' do
       result = service.execute
 
       expect(result[:commands]).to eq "Assigns #{user.to_reference}."
@@ -47,21 +47,21 @@ describe PreviewMarkdownService do
     let(:params) do
       {
         text: "My work\n/estimate 2y",
-        slash_commands_target_type: 'MergeRequest'
+        quick_actions_target_type: 'MergeRequest'
       }
     end
     let(:service) { described_class.new(project, user, params) }
 
-    it 'removes slash commands from text' do
+    it 'removes quick actions from text' do
       result = service.execute
 
       expect(result[:text]).to eq 'My work'
     end
 
-    it 'explains slash commands effect' do
+    it 'explains quick actions effect' do
       result = service.execute
 
       expect(result[:commands]).to eq 'Sets time estimate to 2y.'
-    end    
+    end
   end
 end
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
index 0d6dd28e33218aedf4c630d0e77e8ed976a60f4d..697dc18feb02f462af938d95d7fe87553c832fc5 100644
--- a/spec/services/projects/destroy_service_spec.rb
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -15,8 +15,9 @@ describe Projects::DestroyService, services: true do
   shared_examples 'deleting the project' do
     it 'deletes the project' do
       expect(Project.unscoped.all).not_to include(project)
-      expect(Dir.exist?(path)).to be_falsey
-      expect(Dir.exist?(remove_path)).to be_falsey
+
+      expect(project.gitlab_shell.exists?(project.repository_storage_path, path + '.git')).to be_falsey
+      expect(project.gitlab_shell.exists?(project.repository_storage_path, remove_path + '.git')).to be_falsey
     end
   end
 
diff --git a/spec/services/projects/housekeeping_service_spec.rb b/spec/services/projects/housekeeping_service_spec.rb
index fff12beed71eea10e9b2b79ab96b0460ce02ca91..ebed802708d2b8c8a9d12bcf0f51369e75e343de 100644
--- a/spec/services/projects/housekeeping_service_spec.rb
+++ b/spec/services/projects/housekeeping_service_spec.rb
@@ -66,14 +66,14 @@ describe Projects::HousekeepingService do
     allow(subject).to receive(:lease_key).and_return(:the_lease_key)
 
     # At push 200
-    expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :gc, :the_lease_key, :the_uuid).
-      exactly(1).times
+    expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :gc, :the_lease_key, :the_uuid)
+      .exactly(1).times
     # At push 50, 100, 150
-    expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :full_repack, :the_lease_key, :the_uuid).
-      exactly(3).times
+    expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :full_repack, :the_lease_key, :the_uuid)
+      .exactly(3).times
     # At push 10, 20, ... (except those above)
-    expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :incremental_repack, :the_lease_key, :the_uuid).
-      exactly(16).times
+    expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :incremental_repack, :the_lease_key, :the_uuid)
+      .exactly(16).times
 
     201.times do
       subject.increment!
diff --git a/spec/services/projects/import_service_spec.rb b/spec/services/projects/import_service_spec.rb
index 44db299812f48e53eb4de31a2e292f7333b627ae..e855de38037ffec7ea2d2339198c1822a12a7c89 100644
--- a/spec/services/projects/import_service_spec.rb
+++ b/spec/services/projects/import_service_spec.rb
@@ -111,11 +111,11 @@ describe Projects::ImportService, services: true do
       end
 
       it 'flushes various caches' do
-        allow_any_instance_of(Repository).to receive(:fetch_remote).
-          and_return(true)
+        allow_any_instance_of(Repository).to receive(:fetch_remote)
+          .and_return(true)
 
-        allow_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).
-          and_return(true)
+        allow_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute)
+          .and_return(true)
 
         expect_any_instance_of(Repository).to receive(:expire_content_cache)
 
diff --git a/spec/services/projects/propagate_service_template_spec.rb b/spec/services/projects/propagate_service_template_spec.rb
index 8a6a9f09f7457631aa1606b0eff430e04c417fb3..a6d43c4f0f18c0f657f9cfe39600fcce8c84b73a 100644
--- a/spec/services/projects/propagate_service_template_spec.rb
+++ b/spec/services/projects/propagate_service_template_spec.rb
@@ -60,8 +60,8 @@ describe Projects::PropagateServiceTemplate, services: true do
       Service.build_from_template(project.id, service_template).save!
       Service.build_from_template(project.id, other_service).save!
 
-      expect { described_class.propagate(service_template) }.
-        not_to change { Service.count }
+      expect { described_class.propagate(service_template) }
+        .not_to change { Service.count }
     end
 
     it 'creates the service containing the template attributes' do
@@ -90,8 +90,8 @@ describe Projects::PropagateServiceTemplate, services: true do
       it 'updates the project external tracker' do
         service_template.update!(category: 'issue_tracker', default: false)
 
-        expect { described_class.propagate(service_template) }.
-          to change { project.reload.has_external_issue_tracker }.to(true)
+        expect { described_class.propagate(service_template) }
+          .to change { project.reload.has_external_issue_tracker }.to(true)
       end
     end
 
@@ -99,8 +99,8 @@ describe Projects::PropagateServiceTemplate, services: true do
       it 'updates the project external tracker' do
         service_template.update!(type: 'ExternalWikiService')
 
-        expect { described_class.propagate(service_template) }.
-          to change { project.reload.has_external_wiki }.to(true)
+        expect { described_class.propagate(service_template) }
+          .to change { project.reload.has_external_wiki }.to(true)
       end
     end
   end
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index 5d2f4cf17fb69871df18242e22f1966534fada38..441a5276c5615601643ecb8baaab1028b56d5361 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -7,10 +7,10 @@ describe Projects::TransferService, services: true do
 
   context 'namespace -> namespace' do
     before do
-      allow_any_instance_of(Gitlab::UploadsTransfer).
-        to receive(:move_project).and_return(true)
-      allow_any_instance_of(Gitlab::PagesTransfer).
-        to receive(:move_project).and_return(true)
+      allow_any_instance_of(Gitlab::UploadsTransfer)
+        .to receive(:move_project).and_return(true)
+      allow_any_instance_of(Gitlab::PagesTransfer)
+        .to receive(:move_project).and_return(true)
       group.add_owner(user)
       @result = transfer_project(project, user, group)
     end
@@ -19,6 +19,73 @@ describe Projects::TransferService, services: true do
     it { expect(project.namespace).to eq(group) }
   end
 
+  context 'when transfer succeeds' do
+    before do
+      group.add_owner(user)
+    end
+
+    it 'sends notifications' do
+      expect_any_instance_of(NotificationService).to receive(:project_was_moved)
+
+      transfer_project(project, user, group)
+    end
+
+    it 'expires full_path cache' do
+      expect(project).to receive(:expires_full_path_cache)
+
+      transfer_project(project, user, group)
+    end
+
+    it 'executes system hooks' do
+      expect_any_instance_of(Projects::TransferService).to receive(:execute_system_hooks)
+
+      transfer_project(project, user, group)
+    end
+  end
+
+  context 'when transfer fails' do
+    let!(:original_path) { project_path(project) }
+
+    def attempt_project_transfer
+      expect do
+        transfer_project(project, user, group)
+      end.to raise_error(ActiveRecord::ActiveRecordError)
+    end
+
+    before do
+      group.add_owner(user)
+
+      expect_any_instance_of(Labels::TransferService).to receive(:execute).and_raise(ActiveRecord::StatementInvalid, "PG ERROR")
+    end
+
+    def project_path(project)
+      File.join(project.repository_storage_path, "#{project.path_with_namespace}.git")
+    end
+
+    def current_path
+      project_path(project)
+    end
+
+    it 'rolls back repo location' do
+      attempt_project_transfer
+
+      expect(Dir.exist?(original_path)).to be_truthy
+      expect(original_path).to eq current_path
+    end
+
+    it "doesn't send move notifications" do
+      expect_any_instance_of(NotificationService).not_to receive(:project_was_moved)
+
+      attempt_project_transfer
+    end
+
+    it "doesn't run system hooks" do
+      expect_any_instance_of(Projects::TransferService).not_to receive(:execute_system_hooks)
+
+      attempt_project_transfer
+    end
+  end
+
   context 'namespace -> no namespace' do
     before do
       @result = transfer_project(project, user, nil)
@@ -112,9 +179,9 @@ describe Projects::TransferService, services: true do
     end
 
     it 'only schedules a single job for every user' do
-      expect(UserProjectAccessChangedService).to receive(:new).
-        with([owner.id, group_member.id]).
-        and_call_original
+      expect(UserProjectAccessChangedService).to receive(:new)
+        .with([owner.id, group_member.id])
+        .and_call_original
 
       transfer_project(project, owner, group)
     end
diff --git a/spec/services/projects/unlink_fork_service_spec.rb b/spec/services/projects/unlink_fork_service_spec.rb
index 23f5555d3e0e98af66c906adf887af50a8deaa54..d34652bd7acf9e01231c597dacef013aecd0155e 100644
--- a/spec/services/projects/unlink_fork_service_spec.rb
+++ b/spec/services/projects/unlink_fork_service_spec.rb
@@ -12,9 +12,9 @@ describe Projects::UnlinkForkService, services: true do
     let(:mr_close_service) { MergeRequests::CloseService.new(fork_project, user) }
 
     before do
-      allow(MergeRequests::CloseService).to receive(:new).
-        with(fork_project, user).
-        and_return(mr_close_service)
+      allow(MergeRequests::CloseService).to receive(:new)
+        .with(fork_project, user)
+        .and_return(mr_close_service)
     end
 
     it 'close all pending merge requests' do
diff --git a/spec/services/slash_commands/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb
similarity index 96%
rename from spec/services/slash_commands/interpret_service_spec.rb
rename to spec/services/quick_actions/interpret_service_spec.rb
index c12fb1a6e531aa457aba50e9bfa0063ee331ff60..35373675894393b218bfa7b0b46601bbfc7996b1 100644
--- a/spec/services/slash_commands/interpret_service_spec.rb
+++ b/spec/services/quick_actions/interpret_service_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe SlashCommands::InterpretService, services: true do
+describe QuickActions::InterpretService, services: true do
   let(:project) { create(:empty_project, :public) }
   let(:developer) { create(:user) }
   let(:developer2) { create(:user) }
@@ -359,18 +359,18 @@ describe SlashCommands::InterpretService, services: true do
       let(:content) { "/assign @#{developer.username}" }
 
       context 'Issue' do
-        it 'fetches assignee and populates assignee_id if content contains /assign' do
+        it 'fetches assignee and populates assignee_ids if content contains /assign' do
           _, updates = service.execute(content, issue)
 
-          expect(updates).to eq(assignee_ids: [developer.id])
+          expect(updates[:assignee_ids]).to match_array([developer.id])
         end
       end
 
       context 'Merge Request' do
-        it 'fetches assignee and populates assignee_id if content contains /assign' do
+        it 'fetches assignee and populates assignee_ids if content contains /assign' do
           _, updates = service.execute(content, merge_request)
 
-          expect(updates).to eq(assignee_id: developer.id)
+          expect(updates).to eq(assignee_ids: [developer.id])
         end
       end
     end
@@ -383,7 +383,7 @@ describe SlashCommands::InterpretService, services: true do
       end
 
       context 'Issue' do
-        it 'fetches assignee and populates assignee_id if content contains /assign' do
+        it 'fetches assignee and populates assignee_ids if content contains /assign' do
           _, updates = service.execute(content, issue)
 
           expect(updates[:assignee_ids]).to match_array([developer.id])
@@ -391,10 +391,10 @@ describe SlashCommands::InterpretService, services: true do
       end
 
       context 'Merge Request' do
-        it 'fetches assignee and populates assignee_id if content contains /assign' do
+        it 'fetches assignee and populates assignee_ids if content contains /assign' do
           _, updates = service.execute(content, merge_request)
 
-          expect(updates).to eq(assignee_id: developer.id)
+          expect(updates).to eq(assignee_ids: [developer.id])
         end
       end
     end
@@ -422,11 +422,27 @@ describe SlashCommands::InterpretService, services: true do
       end
 
       context 'Merge Request' do
-        it 'populates assignee_id: nil if content contains /unassign' do
-          merge_request.update(assignee_id: developer.id)
+        it 'populates assignee_ids: [] if content contains /unassign' do
+          merge_request.update(assignee_ids: [developer.id])
           _, updates = service.execute(content, merge_request)
 
-          expect(updates).to eq(assignee_id: nil)
+          expect(updates).to eq(assignee_ids: [])
+        end
+      end
+    end
+
+    context 'reassign command' do
+      let(:content) { '/reassign' }
+
+      context 'Issue' do
+        it 'reassigns user if content contains /reassign @user' do
+          user = create(:user)
+
+          issue.update(assignee_ids: [developer.id])
+
+          _, updates = service.execute("/reassign @#{user.username}", issue)
+
+          expect(updates).to eq(assignee_ids: [user.id])
         end
       end
     end
diff --git a/spec/services/submit_usage_ping_service_spec.rb b/spec/services/submit_usage_ping_service_spec.rb
index 63a1e78f274c9001c64fa6ea1053bb0ce290c86d..817fa4262d5647ad59366b03ae338be2e1baa4bd 100644
--- a/spec/services/submit_usage_ping_service_spec.rb
+++ b/spec/services/submit_usage_ping_service_spec.rb
@@ -92,8 +92,8 @@ describe SubmitUsagePingService do
   end
 
   def stub_response(body)
-    stub_request(:post, 'https://version.gitlab.com/usage_data').
-      to_return(
+    stub_request(:post, 'https://version.gitlab.com/usage_data')
+      .to_return(
         headers: { 'Content-Type' => 'application/json' },
         body: body.to_json
       )
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 9295c09aefc5499941c7f419ebb346c8065e237a..e35e4c1d6314e5bc75078b215bfac053d39657fa 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -333,8 +333,8 @@ describe SystemNoteService, services: true do
       end
 
       it 'sets the note text' do
-        expect(subject.note).
-          to eq "changed title from **{-Old title-}** to **{+Lorem ipsum+}**"
+        expect(subject.note)
+          .to eq "changed title from **{-Old title-}** to **{+Lorem ipsum+}**"
       end
     end
   end
@@ -521,8 +521,8 @@ describe SystemNoteService, services: true do
     context 'when mentioner is not a MergeRequest' do
       it 'is falsey' do
         mentioner = noteable.dup
-        expect(described_class.cross_reference_disallowed?(noteable, mentioner)).
-          to be_falsey
+        expect(described_class.cross_reference_disallowed?(noteable, mentioner))
+          .to be_falsey
       end
     end
 
@@ -533,14 +533,14 @@ describe SystemNoteService, services: true do
 
       it 'is truthy when noteable is in commits' do
         expect(mentioner).to receive(:commits).and_return([noteable])
-        expect(described_class.cross_reference_disallowed?(noteable, mentioner)).
-          to be_truthy
+        expect(described_class.cross_reference_disallowed?(noteable, mentioner))
+          .to be_truthy
       end
 
       it 'is falsey when noteable is not in commits' do
         expect(mentioner).to receive(:commits).and_return([])
-        expect(described_class.cross_reference_disallowed?(noteable, mentioner)).
-          to be_falsey
+        expect(described_class.cross_reference_disallowed?(noteable, mentioner))
+          .to be_falsey
       end
     end
 
@@ -548,8 +548,8 @@ describe SystemNoteService, services: true do
       let(:noteable) { ExternalIssue.new('EXT-1234', project) }
       it 'is truthy' do
         mentioner = noteable.dup
-        expect(described_class.cross_reference_disallowed?(noteable, mentioner)).
-          to be_truthy
+        expect(described_class.cross_reference_disallowed?(noteable, mentioner))
+          .to be_truthy
       end
     end
   end
@@ -566,13 +566,13 @@ describe SystemNoteService, services: true do
       end
 
       it 'is truthy when already mentioned' do
-        expect(described_class.cross_reference_exists?(noteable, commit0)).
-          to be_truthy
+        expect(described_class.cross_reference_exists?(noteable, commit0))
+          .to be_truthy
       end
 
       it 'is falsey when not already mentioned' do
-        expect(described_class.cross_reference_exists?(noteable, commit1)).
-          to be_falsey
+        expect(described_class.cross_reference_exists?(noteable, commit1))
+          .to be_falsey
       end
 
       context 'legacy capitalized cross reference' do
@@ -583,8 +583,8 @@ describe SystemNoteService, services: true do
         end
 
         it 'is truthy when already mentioned' do
-          expect(described_class.cross_reference_exists?(noteable, commit0)).
-            to be_truthy
+          expect(described_class.cross_reference_exists?(noteable, commit0))
+            .to be_truthy
         end
       end
     end
@@ -596,13 +596,13 @@ describe SystemNoteService, services: true do
       end
 
       it 'is truthy when already mentioned' do
-        expect(described_class.cross_reference_exists?(commit0, commit1)).
-          to be_truthy
+        expect(described_class.cross_reference_exists?(commit0, commit1))
+          .to be_truthy
       end
 
       it 'is falsey when not already mentioned' do
-        expect(described_class.cross_reference_exists?(commit1, commit0)).
-          to be_falsey
+        expect(described_class.cross_reference_exists?(commit1, commit0))
+          .to be_falsey
       end
 
       context 'legacy capitalized cross reference' do
@@ -613,8 +613,8 @@ describe SystemNoteService, services: true do
         end
 
         it 'is truthy when already mentioned' do
-          expect(described_class.cross_reference_exists?(commit0, commit1)).
-            to be_truthy
+          expect(described_class.cross_reference_exists?(commit0, commit1))
+            .to be_truthy
         end
       end
     end
@@ -629,8 +629,8 @@ describe SystemNoteService, services: true do
       end
 
       it 'is true when a fork mentions an external issue' do
-        expect(described_class.cross_reference_exists?(noteable, commit2)).
-            to be true
+        expect(described_class.cross_reference_exists?(noteable, commit2))
+            .to be true
       end
 
       context 'legacy capitalized cross reference' do
@@ -640,8 +640,8 @@ describe SystemNoteService, services: true do
         end
 
         it 'is true when a fork mentions an external issue' do
-          expect(described_class.cross_reference_exists?(noteable, commit2)).
-              to be true
+          expect(described_class.cross_reference_exists?(noteable, commit2))
+              .to be true
         end
       end
     end
@@ -807,7 +807,7 @@ describe SystemNoteService, services: true do
             body: hash_including(
               GlobalID: "GitLab",
               object: {
-                url: namespace_project_commit_url(project.namespace, project, commit),
+                url: project_commit_url(project, commit),
                 title: "GitLab: Mentioned on commit - #{commit.title}",
                 icon: { title: "GitLab", url16x16: "https://gitlab.com/favicon.ico" },
                 status: { resolved: false }
@@ -833,7 +833,7 @@ describe SystemNoteService, services: true do
             body: hash_including(
               GlobalID: "GitLab",
               object: {
-                url: namespace_project_issue_url(project.namespace, project, issue),
+                url: project_issue_url(project, issue),
                 title: "GitLab: Mentioned on issue - #{issue.title}",
                 icon: { title: "GitLab", url16x16: "https://gitlab.com/favicon.ico" },
                 status: { resolved: false }
@@ -859,7 +859,7 @@ describe SystemNoteService, services: true do
             body: hash_including(
               GlobalID: "GitLab",
               object: {
-                url: namespace_project_snippet_url(project.namespace, project, snippet),
+                url: project_snippet_url(project, snippet),
                 title: "GitLab: Mentioned on snippet - #{snippet.title}",
                 icon: { title: "GitLab", url16x16: "https://gitlab.com/favicon.ico" },
                 status: { resolved: false }
@@ -1098,7 +1098,7 @@ describe SystemNoteService, services: true do
 
       diff_id = merge_request.merge_request_diff.id
       line_code = change_position.line_code(project.repository)
-      expect(subject.note).to include(diffs_namespace_project_merge_request_url(project.namespace, project, merge_request, diff_id: diff_id, anchor: line_code))
+      expect(subject.note).to include(diffs_project_merge_request_url(project, merge_request, diff_id: diff_id, anchor: line_code))
     end
   end
 end
diff --git a/spec/services/tags/create_service_spec.rb b/spec/services/tags/create_service_spec.rb
index b9121b1de499c1d0cfb6317c02dbd949b64dd4f5..9f143cc566745f79421d0053f1f01da7ef7c7723 100644
--- a/spec/services/tags/create_service_spec.rb
+++ b/spec/services/tags/create_service_spec.rb
@@ -26,9 +26,9 @@ describe Tags::CreateService, services: true do
 
     context 'when tag already exists' do
       it 'returns an error' do
-        expect(repository).to receive(:add_tag).
-          with(user, 'v1.1.0', 'master', 'Foo').
-          and_raise(Rugged::TagError)
+        expect(repository).to receive(:add_tag)
+          .with(user, 'v1.1.0', 'master', 'Foo')
+          .and_raise(Rugged::TagError)
 
         response = service.execute('v1.1.0', 'master', 'Foo')
 
@@ -39,9 +39,9 @@ describe Tags::CreateService, services: true do
 
     context 'when pre-receive hook fails' do
       it 'returns an error' do
-        expect(repository).to receive(:add_tag).
-          with(user, 'v1.1.0', 'master', 'Foo').
-          and_raise(GitHooksService::PreReceiveError, 'something went wrong')
+        expect(repository).to receive(:add_tag)
+          .with(user, 'v1.1.0', 'master', 'Foo')
+          .and_raise(GitHooksService::PreReceiveError, 'something went wrong')
 
         response = service.execute('v1.1.0', 'master', 'Foo')
 
diff --git a/spec/services/user_project_access_changed_service_spec.rb b/spec/services/user_project_access_changed_service_spec.rb
index b4efe7de4318cc64147ed9ba6423076861468889..14a5e40350a7d1bb042ccb7c27f2636dbd2c0818 100644
--- a/spec/services/user_project_access_changed_service_spec.rb
+++ b/spec/services/user_project_access_changed_service_spec.rb
@@ -3,8 +3,8 @@ 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]])
+      expect(AuthorizedProjectsWorker).to receive(:bulk_perform_and_wait)
+        .with([[1], [2]])
 
       described_class.new([1, 2]).execute
     end
diff --git a/spec/services/users/activity_service_spec.rb b/spec/services/users/activity_service_spec.rb
index 8d67ebe3231e55967d538f1a906a2caa7d454c3a..2e009d4ce1c28ea61a468c997a9c546c8aaa6547 100644
--- a/spec/services/users/activity_service_spec.rb
+++ b/spec/services/users/activity_service_spec.rb
@@ -41,8 +41,8 @@ describe Users::ActivityService, services: true do
   end
 
   def last_hour_user_ids
-    Gitlab::UserActivities.new.
-      select { |k, v| v >= 1.hour.ago.to_i.to_s }.
-      map { |k, _| k.to_i }
+    Gitlab::UserActivities.new
+      .select { |k, v| v >= 1.hour.ago.to_i.to_s }
+      .map { |k, _| k.to_i }
   end
 end
diff --git a/spec/services/users/refresh_authorized_projects_service_spec.rb b/spec/services/users/refresh_authorized_projects_service_spec.rb
index 8c40d25e00c1b3942c35d221e6e637e79affa7bc..b65cadbb2f593bb111c0d3f0a283b60342e01e2b 100644
--- a/spec/services/users/refresh_authorized_projects_service_spec.rb
+++ b/spec/services/users/refresh_authorized_projects_service_spec.rb
@@ -10,11 +10,11 @@ describe Users::RefreshAuthorizedProjectsService do
 
   describe '#execute', :redis do
     it 'refreshes the authorizations using a lease' do
-      expect_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain).
-        and_return('foo')
+      expect_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain)
+        .and_return('foo')
 
-      expect(Gitlab::ExclusiveLease).to receive(:cancel).
-        with(an_instance_of(String), 'foo')
+      expect(Gitlab::ExclusiveLease).to receive(:cancel)
+        .with(an_instance_of(String), 'foo')
 
       expect(service).to receive(:execute_without_lease)
 
@@ -29,11 +29,11 @@ describe Users::RefreshAuthorizedProjectsService do
 
     it 'updates the authorized projects of the user' do
       project2 = create(:empty_project)
-      to_remove = user.project_authorizations.
-        create!(project: project2, access_level: Gitlab::Access::MASTER)
+      to_remove = user.project_authorizations
+        .create!(project: project2, access_level: Gitlab::Access::MASTER)
 
-      expect(service).to receive(:update_authorizations).
-        with([to_remove.project_id], [[user.id, project.id, Gitlab::Access::MASTER]])
+      expect(service).to receive(:update_authorizations)
+        .with([to_remove.project_id], [[user.id, project.id, Gitlab::Access::MASTER]])
 
       service.execute_without_lease
     end
@@ -41,11 +41,11 @@ describe Users::RefreshAuthorizedProjectsService do
     it 'sets the access level of a project to the highest available level' do
       user.project_authorizations.delete_all
 
-      to_remove = user.project_authorizations.
-        create!(project: project, access_level: Gitlab::Access::DEVELOPER)
+      to_remove = user.project_authorizations
+        .create!(project: project, access_level: Gitlab::Access::DEVELOPER)
 
-      expect(service).to receive(:update_authorizations).
-        with([to_remove.project_id], [[user.id, project.id, Gitlab::Access::MASTER]])
+      expect(service).to receive(:update_authorizations)
+        .with([to_remove.project_id], [[user.id, project.id, Gitlab::Access::MASTER]])
 
       service.execute_without_lease
     end
diff --git a/spec/services/users/update_service_spec.rb b/spec/services/users/update_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0b2f840c462d6084ffcac3742cce10a8bf1f7847
--- /dev/null
+++ b/spec/services/users/update_service_spec.rb
@@ -0,0 +1,43 @@
+require 'spec_helper'
+
+describe Users::UpdateService, services: true do
+  let(:user) { create(:user) }
+
+  describe '#execute' do
+    it 'updates the name' do
+      result = update_user(user, name: 'New Name')
+
+      expect(result).to eq(status: :success)
+      expect(user.name).to eq('New Name')
+    end
+
+    it 'returns an error result when record cannot be updated' do
+      expect do
+        update_user(user, { email: 'invalid' })
+      end.not_to change { user.reload.email }
+    end
+
+    def update_user(user, opts)
+      described_class.new(user, opts).execute
+    end
+  end
+
+  describe '#execute!' do
+    it 'updates the name' do
+      result = update_user(user, name: 'New Name')
+
+      expect(result).to be true
+      expect(user.name).to eq('New Name')
+    end
+
+    it 'raises an error when record cannot be updated' do
+      expect do
+        update_user(user, email: 'invalid')
+      end.to raise_error(ActiveRecord::RecordInvalid)
+    end
+
+    def update_user(user, opts)
+      described_class.new(user, opts).execute!
+    end
+  end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 01ac3cbd3f6b62204ff043699f073b192f8fcf5d..3e90a642d56974dd745185c44c9fea1820449773 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -44,6 +44,7 @@ RSpec.configure do |config|
 
   config.include Devise::Test::ControllerHelpers, type: :controller
   config.include Devise::Test::ControllerHelpers, type: :view
+  config.include Devise::Test::IntegrationHelpers, type: :feature
   config.include Warden::Test::Helpers, type: :request
   config.include LoginHelpers, type: :feature
   config.include SearchHelpers, type: :feature
@@ -56,7 +57,7 @@ RSpec.configure do |config|
   config.include StubGitlabCalls
   config.include StubGitlabData
   config.include ApiHelpers, :api
-  config.include Rails.application.routes.url_helpers, type: :routing
+  config.include Gitlab::Routing.url_helpers, type: :routing
   config.include MigrationsHelpers, :migration
 
   config.infer_spec_type_from_file_location!
diff --git a/spec/support/api/schema_matcher.rb b/spec/support/api/schema_matcher.rb
index e42d727672be009d12e71c0428e28aca0bd91ad7..dff0dfba675c0de15e55a3cd18b9c5253b8c497e 100644
--- a/spec/support/api/schema_matcher.rb
+++ b/spec/support/api/schema_matcher.rb
@@ -1,8 +1,16 @@
+def schema_path(schema)
+  schema_directory = "#{Dir.pwd}/spec/fixtures/api/schemas"
+  "#{schema_directory}/#{schema}.json"
+end
+
 RSpec::Matchers.define :match_response_schema do |schema, **options|
   match do |response|
-    schema_directory = "#{Dir.pwd}/spec/fixtures/api/schemas"
-    schema_path = "#{schema_directory}/#{schema}.json"
+    JSON::Validator.validate!(schema_path(schema), response.body, options)
+  end
+end
 
-    JSON::Validator.validate!(schema_path, response.body, options)
+RSpec::Matchers.define :match_schema do |schema, **options|
+  match do |data|
+    JSON::Validator.validate!(schema_path(schema), data, options)
   end
 end
diff --git a/spec/support/api/scopes/read_user_shared_examples.rb b/spec/support/api/scopes/read_user_shared_examples.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3bd589d64b92eb0541d0463b44835476e1e31b11
--- /dev/null
+++ b/spec/support/api/scopes/read_user_shared_examples.rb
@@ -0,0 +1,79 @@
+shared_examples_for 'allows the "read_user" scope' do
+  context 'for personal access tokens' do
+    context 'when the requesting token has the "api" scope' do
+      let(:token) { create(:personal_access_token, scopes: ['api'], user: user) }
+
+      it 'returns a "200" response' do
+        get api_call.call(path, user, personal_access_token: token)
+
+        expect(response).to have_http_status(200)
+      end
+    end
+
+    context 'when the requesting token has the "read_user" scope' do
+      let(:token) { create(:personal_access_token, scopes: ['read_user'], user: user) }
+
+      it 'returns a "200" response' do
+        get api_call.call(path, user, personal_access_token: token)
+
+        expect(response).to have_http_status(200)
+      end
+    end
+
+    context 'when the requesting token does not have any required scope' do
+      let(:token) { create(:personal_access_token, scopes: ['read_registry'], user: user) }
+
+      it 'returns a "401" response' do
+        get api_call.call(path, user, personal_access_token: token)
+
+        expect(response).to have_http_status(401)
+      end
+    end
+  end
+
+  context 'for doorkeeper (OAuth) tokens' do
+    let!(:application) { Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user) }
+
+    context 'when the requesting token has the "api" scope' do
+      let!(:token) { Doorkeeper::AccessToken.create! application_id: application.id, resource_owner_id: user.id, scopes: "api" }
+
+      it 'returns a "200" response' do
+        get api_call.call(path, user, oauth_access_token: token)
+
+        expect(response).to have_http_status(200)
+      end
+    end
+
+    context 'when the requesting token has the "read_user" scope' do
+      let!(:token) { Doorkeeper::AccessToken.create! application_id: application.id, resource_owner_id: user.id, scopes: "read_user" }
+
+      it 'returns a "200" response' do
+        get api_call.call(path, user, oauth_access_token: token)
+
+        expect(response).to have_http_status(200)
+      end
+    end
+
+    context 'when the requesting token does not have any required scope' do
+      let!(:token) { Doorkeeper::AccessToken.create! application_id: application.id, resource_owner_id: user.id, scopes: "invalid" }
+
+      it 'returns a "403" response' do
+        get api_call.call(path, user, oauth_access_token: token)
+
+        expect(response).to have_http_status(403)
+      end
+    end
+  end
+end
+
+shared_examples_for 'does not allow the "read_user" scope' do
+  context 'when the requesting token has the "read_user" scope' do
+    let(:token) { create(:personal_access_token, scopes: ['read_user'], user: user) }
+
+    it 'returns a "401" response' do
+      post api_call.call(path, user, personal_access_token: token), attributes_for(:user, projects_limit: 3)
+
+      expect(response).to have_http_status(401)
+    end
+  end
+end
diff --git a/spec/support/api_helpers.rb b/spec/support/api_helpers.rb
index 35d1e1cfc7dbb95c4b687b7d07feb9ee288716bb..ac0aaa524b73e61afc8670359c574380ac74ffef 100644
--- a/spec/support/api_helpers.rb
+++ b/spec/support/api_helpers.rb
@@ -17,14 +17,18 @@ module ApiHelpers
   #   => "/api/v2/issues?foo=bar&private_token=..."
   #
   # Returns the relative path to the requested API resource
-  def api(path, user = nil, version: API::API.version)
+  def api(path, user = nil, version: API::API.version, personal_access_token: nil, oauth_access_token: nil)
     "/api/#{version}#{path}" +
 
       # Normalize query string
       (path.index('?') ? '' : '?') +
 
+      if personal_access_token.present?
+        "&private_token=#{personal_access_token.token}"
+      elsif oauth_access_token.present?
+        "&access_token=#{oauth_access_token.token}"
       # Append private_token if given a User object
-      if user.respond_to?(:private_token)
+      elsif user.respond_to?(:private_token)
         "&private_token=#{user.private_token}"
       else
         ''
@@ -32,8 +36,14 @@ module ApiHelpers
   end
 
   # Temporary helper method for simplifying V3 exclusive API specs
-  def v3_api(path, user = nil)
-    api(path, user, version: 'v3')
+  def v3_api(path, user = nil, personal_access_token: nil, oauth_access_token: nil)
+    api(
+      path,
+      user,
+      version: 'v3',
+      personal_access_token: personal_access_token,
+      oauth_access_token: oauth_access_token
+    )
   end
 
   def ci_api(path, user = nil)
diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb
index c34e76fa72fc5c1fd29c6aa76e4929a1d2fca6bc..3e5d6cf1364d0a85628732de4eebd893f33b63aa 100644
--- a/spec/support/capybara.rb
+++ b/spec/support/capybara.rb
@@ -35,4 +35,14 @@ RSpec.configure do |config|
     TestEnv.eager_load_driver_server
     $capybara_server_already_started = true
   end
+
+  config.after(:each, :js) do |example|
+    # capybara/rspec already calls Capybara.reset_sessions! in an `after` hook,
+    # but `block_and_wait_for_requests_complete` is called before it so by
+    # calling it explicitely here, we prevent any new requests from being fired
+    # See https://github.com/teamcapybara/capybara/blob/ffb41cfad620de1961bb49b1562a9fa9b28c0903/lib/capybara/rspec.rb#L20-L25
+    # We don't reset the session when the example failed, because we need capybara-screenshot to have access to it.
+    Capybara.reset_sessions! unless example.exception
+    block_and_wait_for_requests_complete
+  end
 end
diff --git a/spec/support/chat_slash_commands_shared_examples.rb b/spec/support/chat_slash_commands_shared_examples.rb
index 4dfa29849ee7c4fc0ff9a224a3be904a2f75b25a..978b0b9cc30b2df6cbebf108855e6eabb876a77c 100644
--- a/spec/support/chat_slash_commands_shared_examples.rb
+++ b/spec/support/chat_slash_commands_shared_examples.rb
@@ -87,7 +87,7 @@ RSpec.shared_examples 'chat slash commands service' do
         end
 
         it 'triggers the command' do
-          expect_any_instance_of(Gitlab::ChatCommands::Command).to receive(:execute)
+          expect_any_instance_of(Gitlab::SlashCommands::Command).to receive(:execute)
 
           subject.trigger(params)
         end
diff --git a/spec/support/controllers/githubish_import_controller_shared_examples.rb b/spec/support/controllers/githubish_import_controller_shared_examples.rb
index d6b40db09ce3b05e7aae92a4a1703fa45d72703a..a8d9566b4e496b54c258d9b442decd56b24b286a 100644
--- a/spec/support/controllers/githubish_import_controller_shared_examples.rb
+++ b/spec/support/controllers/githubish_import_controller_shared_examples.rb
@@ -14,8 +14,8 @@ shared_examples 'a GitHub-ish import controller: POST personal_access_token' do
   it "updates access token" do
     token = 'asdfasdf9876'
 
-    allow_any_instance_of(Gitlab::GithubImport::Client).
-      to receive(:user).and_return(true)
+    allow_any_instance_of(Gitlab::GithubImport::Client)
+      .to receive(:user).and_return(true)
 
     post :personal_access_token, personal_access_token: token
 
@@ -79,8 +79,8 @@ shared_examples 'a GitHub-ish import controller: GET status' do
   end
 
   it "handles an invalid access token" do
-    allow_any_instance_of(Gitlab::GithubImport::Client).
-      to receive(:repos).and_raise(Octokit::Unauthorized)
+    allow_any_instance_of(Gitlab::GithubImport::Client)
+      .to receive(:repos).and_raise(Octokit::Unauthorized)
 
     get :status
 
@@ -110,9 +110,9 @@ shared_examples 'a GitHub-ish import controller: POST create' do
   context "when the repository owner is the provider user" do
     context "when the provider user and GitLab user's usernames match" do
       it "takes the current user's namespace" do
-        expect(Gitlab::GithubImport::ProjectCreator).
-          to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider).
-            and_return(double(execute: true))
+        expect(Gitlab::GithubImport::ProjectCreator)
+          .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider)
+            .and_return(double(execute: true))
 
         post :create, format: :js
       end
@@ -122,9 +122,9 @@ shared_examples 'a GitHub-ish import controller: POST create' do
       let(:provider_username) { "someone_else" }
 
       it "takes the current user's namespace" do
-        expect(Gitlab::GithubImport::ProjectCreator).
-          to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider).
-            and_return(double(execute: true))
+        expect(Gitlab::GithubImport::ProjectCreator)
+          .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider)
+            .and_return(double(execute: true))
 
         post :create, format: :js
       end
@@ -144,9 +144,9 @@ shared_examples 'a GitHub-ish import controller: POST create' do
 
       context "when the namespace is owned by the GitLab user" do
         it "takes the existing namespace" do
-          expect(Gitlab::GithubImport::ProjectCreator).
-            to receive(:new).with(provider_repo, provider_repo.name, existing_namespace, user, access_params, type: provider).
-              and_return(double(execute: true))
+          expect(Gitlab::GithubImport::ProjectCreator)
+            .to receive(:new).with(provider_repo, provider_repo.name, existing_namespace, user, access_params, type: provider)
+              .and_return(double(execute: true))
 
           post :create, format: :js
         end
@@ -159,9 +159,9 @@ shared_examples 'a GitHub-ish import controller: POST create' do
         end
 
         it "creates a project using user's namespace" do
-          expect(Gitlab::GithubImport::ProjectCreator).
-            to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider).
-              and_return(double(execute: true))
+          expect(Gitlab::GithubImport::ProjectCreator)
+            .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider)
+              .and_return(double(execute: true))
 
           post :create, format: :js
         end
@@ -171,16 +171,16 @@ shared_examples 'a GitHub-ish import controller: POST create' do
     context "when a namespace with the provider user's username doesn't exist" do
       context "when current user can create namespaces" do
         it "creates the namespace" do
-          expect(Gitlab::GithubImport::ProjectCreator).
-            to receive(:new).and_return(double(execute: true))
+          expect(Gitlab::GithubImport::ProjectCreator)
+            .to receive(:new).and_return(double(execute: true))
 
           expect { post :create, target_namespace: provider_repo.name, format: :js }.to change(Namespace, :count).by(1)
         end
 
         it "takes the new namespace" do
-          expect(Gitlab::GithubImport::ProjectCreator).
-            to receive(:new).with(provider_repo, provider_repo.name, an_instance_of(Group), user, access_params, type: provider).
-              and_return(double(execute: true))
+          expect(Gitlab::GithubImport::ProjectCreator)
+            .to receive(:new).with(provider_repo, provider_repo.name, an_instance_of(Group), user, access_params, type: provider)
+              .and_return(double(execute: true))
 
           post :create, target_namespace: provider_repo.name, format: :js
         end
@@ -192,16 +192,16 @@ shared_examples 'a GitHub-ish import controller: POST create' do
         end
 
         it "doesn't create the namespace" do
-          expect(Gitlab::GithubImport::ProjectCreator).
-            to receive(:new).and_return(double(execute: true))
+          expect(Gitlab::GithubImport::ProjectCreator)
+            .to receive(:new).and_return(double(execute: true))
 
           expect { post :create, format: :js }.not_to change(Namespace, :count)
         end
 
         it "takes the current user's namespace" do
-          expect(Gitlab::GithubImport::ProjectCreator).
-            to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider).
-              and_return(double(execute: true))
+          expect(Gitlab::GithubImport::ProjectCreator)
+            .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider)
+              .and_return(double(execute: true))
 
           post :create, format: :js
         end
@@ -217,17 +217,17 @@ shared_examples 'a GitHub-ish import controller: POST create' do
       end
 
       it 'takes the selected namespace and name' do
-        expect(Gitlab::GithubImport::ProjectCreator).
-          to receive(:new).with(provider_repo, test_name, test_namespace, user, access_params, type: provider).
-            and_return(double(execute: true))
+        expect(Gitlab::GithubImport::ProjectCreator)
+          .to receive(:new).with(provider_repo, test_name, test_namespace, user, access_params, type: provider)
+            .and_return(double(execute: true))
 
         post :create, { target_namespace: test_namespace.name, new_name: test_name, format: :js }
       end
 
       it 'takes the selected name and default namespace' do
-        expect(Gitlab::GithubImport::ProjectCreator).
-          to receive(:new).with(provider_repo, test_name, user.namespace, user, access_params, type: provider).
-            and_return(double(execute: true))
+        expect(Gitlab::GithubImport::ProjectCreator)
+          .to receive(:new).with(provider_repo, test_name, user.namespace, user, access_params, type: provider)
+            .and_return(double(execute: true))
 
         post :create, { new_name: test_name, format: :js }
       end
@@ -243,9 +243,9 @@ shared_examples 'a GitHub-ish import controller: POST create' do
       end
 
       it 'takes the selected namespace and name' do
-        expect(Gitlab::GithubImport::ProjectCreator).
-          to receive(:new).with(provider_repo, test_name, nested_namespace, user, access_params, type: provider).
-            and_return(double(execute: true))
+        expect(Gitlab::GithubImport::ProjectCreator)
+          .to receive(:new).with(provider_repo, test_name, nested_namespace, user, access_params, type: provider)
+            .and_return(double(execute: true))
 
         post :create, { target_namespace: nested_namespace.full_path, new_name: test_name, format: :js }
       end
@@ -255,26 +255,26 @@ shared_examples 'a GitHub-ish import controller: POST create' do
       let(:test_name) { 'test_name' }
 
       it 'takes the selected namespace and name' do
-        expect(Gitlab::GithubImport::ProjectCreator).
-          to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider).
-            and_return(double(execute: true))
+        expect(Gitlab::GithubImport::ProjectCreator)
+          .to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider)
+            .and_return(double(execute: true))
 
         post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :js }
       end
 
       it 'creates the namespaces' do
-        allow(Gitlab::GithubImport::ProjectCreator).
-          to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider).
-            and_return(double(execute: true))
+        allow(Gitlab::GithubImport::ProjectCreator)
+          .to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider)
+            .and_return(double(execute: true))
 
         expect { post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :js } }
           .to change { Namespace.count }.by(2)
       end
 
       it 'new namespace has the right parent' do
-        allow(Gitlab::GithubImport::ProjectCreator).
-          to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider).
-            and_return(double(execute: true))
+        allow(Gitlab::GithubImport::ProjectCreator)
+          .to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider)
+            .and_return(double(execute: true))
 
         post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :js }
 
@@ -287,17 +287,17 @@ shared_examples 'a GitHub-ish import controller: POST create' do
       let!(:parent_namespace) { create(:group, name: 'foo', owner: user) }
 
       it 'takes the selected namespace and name' do
-        expect(Gitlab::GithubImport::ProjectCreator).
-          to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider).
-            and_return(double(execute: true))
+        expect(Gitlab::GithubImport::ProjectCreator)
+          .to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider)
+            .and_return(double(execute: true))
 
         post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :js }
       end
 
       it 'creates the namespaces' do
-        allow(Gitlab::GithubImport::ProjectCreator).
-          to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider).
-            and_return(double(execute: true))
+        allow(Gitlab::GithubImport::ProjectCreator)
+          .to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider)
+            .and_return(double(execute: true))
 
         expect { post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :js } }
           .to change { Namespace.count }.by(2)
diff --git a/spec/support/cycle_analytics_helpers.rb b/spec/support/cycle_analytics_helpers.rb
index 6e1eb5c678de0bf25553ca42845de0c36d23f8a5..c0a5491a430917c2309cf5eef44658cb327b450b 100644
--- a/spec/support/cycle_analytics_helpers.rb
+++ b/spec/support/cycle_analytics_helpers.rb
@@ -74,7 +74,9 @@ module CycleAnalyticsHelpers
 
   def dummy_pipeline
     @dummy_pipeline ||=
-      Ci::Pipeline.new(sha: project.repository.commit('master').sha)
+      Ci::Pipeline.new(
+        sha: project.repository.commit('master').sha,
+        project: project)
   end
 
   def new_dummy_job(environment)
diff --git a/spec/support/fake_migration_classes.rb b/spec/support/fake_migration_classes.rb
index 3de0460c3ca31518700eb591829bbdb74a123e91..b0fc842285732ceda8562e60d50090f93ca92ecf 100644
--- a/spec/support/fake_migration_classes.rb
+++ b/spec/support/fake_migration_classes.rb
@@ -1,3 +1,11 @@
 class FakeRenameReservedPathMigrationV1 < ActiveRecord::Migration
   include Gitlab::Database::RenameReservedPathsMigration::V1
+
+  def version
+    '20170316163845'
+  end
+
+  def name
+    "FakeRenameReservedPathMigrationV1"
+  end
 end
diff --git a/spec/support/features/issuable_slash_commands_shared_examples.rb b/spec/support/features/issuable_slash_commands_shared_examples.rb
index fa82dc5e9f967f35b529a3ae8c31a9f5ffc8d42c..98b014df6cd5677c55d29a71e069ea751a2aba56 100644
--- a/spec/support/features/issuable_slash_commands_shared_examples.rb
+++ b/spec/support/features/issuable_slash_commands_shared_examples.rb
@@ -1,8 +1,8 @@
 # Specifications for behavior common to all objects with executable attributes.
 # It takes a `issuable_type`, and expect an `issuable`.
 
-shared_examples 'issuable record that supports slash commands in its description and notes' do |issuable_type|
-  include SlashCommandsHelpers
+shared_examples 'issuable record that supports quick actions in its description and notes' do |issuable_type|
+  include QuickActionsHelpers
 
   let(:master) { create(:user) }
   let(:assignee) { create(:user, username: 'bob') }
@@ -17,7 +17,7 @@ shared_examples 'issuable record that supports slash commands in its description
     project.team << [master, :master]
     project.team << [assignee, :developer]
     project.team << [guest, :guest]
-    login_with(master)
+    gitlab_sign_in(master)
   end
 
   after do
@@ -28,7 +28,12 @@ shared_examples 'issuable record that supports slash commands in its description
   describe "new #{issuable_type}", js: true do
     context 'with commands in the description' do
       it "creates the #{issuable_type} and interpret commands accordingly" do
-        visit public_send("new_namespace_project_#{issuable_type}_path", project.namespace, project, new_url_opts)
+        case issuable_type
+        when :merge_request
+          visit public_send("namespace_project_new_merge_request_path", project.namespace, project, new_url_opts)
+        when :issue
+          visit public_send("new_namespace_project_issue_path", project.namespace, project, new_url_opts)
+        end
         fill_in "#{issuable_type}_title", with: 'bug 345'
         fill_in "#{issuable_type}_description", with: "bug description\n/label ~bug\n/milestone %\"ASAP\""
         click_button "Submit #{issuable_type}".humanize
@@ -105,8 +110,8 @@ shared_examples 'issuable record that supports slash commands in its description
 
       context "when current user cannot close #{issuable_type}" do
         before do
-          logout
-          login_with(guest)
+          gitlab_sign_out
+          gitlab_sign_in(guest)
           visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable)
         end
 
@@ -140,8 +145,8 @@ shared_examples 'issuable record that supports slash commands in its description
 
       context "when current user cannot reopen #{issuable_type}" do
         before do
-          logout
-          login_with(guest)
+          gitlab_sign_out
+          gitlab_sign_in(guest)
           visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable)
         end
 
@@ -170,8 +175,8 @@ shared_examples 'issuable record that supports slash commands in its description
 
       context "when current user cannot change title of #{issuable_type}" do
         before do
-          logout
-          login_with(guest)
+          gitlab_sign_out
+          gitlab_sign_in(guest)
           visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable)
         end
 
@@ -260,7 +265,7 @@ shared_examples 'issuable record that supports slash commands in its description
   end
 
   describe "preview of note on #{issuable_type}" do
-    it 'removes slash commands from note and explains them' do
+    it 'removes quick actions from note and explains them' do
       visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable)
 
       page.within('.js-main-target-form') do
diff --git a/spec/support/features/reportable_note_shared_examples.rb b/spec/support/features/reportable_note_shared_examples.rb
index 0d80c95e8267e10a8de7d1f7eaf59051a8e2c6ea..27e079c01dd1eeaca0c8c309c91d63bb5a216f44 100644
--- a/spec/support/features/reportable_note_shared_examples.rb
+++ b/spec/support/features/reportable_note_shared_examples.rb
@@ -13,9 +13,7 @@ shared_examples 'reportable note' do
 
   it 'dropdown has Edit, Report and Delete links' do
     dropdown = comment.find(more_actions_selector)
-
-    dropdown.click
-    dropdown.find('.dropdown-menu li', match: :first)
+    open_dropdown(dropdown)
 
     expect(dropdown).to have_button('Edit comment')
     expect(dropdown).to have_link('Report as abuse', href: abuse_report_path)
@@ -24,13 +22,16 @@ shared_examples 'reportable note' do
 
   it 'Report button links to a report page' do
     dropdown = comment.find(more_actions_selector)
-
-    dropdown.click
-    dropdown.find('.dropdown-menu li', match: :first)
+    open_dropdown(dropdown)
 
     dropdown.click_link('Report as abuse')
 
     expect(find('#user_name')['value']).to match(note.author.username)
     expect(find('#abuse_report_message')['value']).to match(noteable_note_url(note))
   end
+
+  def open_dropdown(dropdown)
+    dropdown.click
+    dropdown.find('.dropdown-menu li', match: :first)
+  end
 end
diff --git a/spec/support/filter_item_select_helper.rb b/spec/support/filter_item_select_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..519e84d359e5ac54ea81ae7d07d39f02937cbd0b
--- /dev/null
+++ b/spec/support/filter_item_select_helper.rb
@@ -0,0 +1,19 @@
+# Helper allows you to select value from filter-items
+#
+# Params
+#   value - value for select
+#   selector - css selector of item
+#
+# Usage:
+#
+#   filter_item_select('Any Author', '.js-author-search')
+#
+module FilterItemSelectHelper
+  def filter_item_select(value, selector)
+    find(selector).click
+    wait_for_requests
+    page.within('.dropdown-content') do
+      click_link value
+    end
+  end
+end
diff --git a/spec/support/filtered_search_helpers.rb b/spec/support/filtered_search_helpers.rb
index 37cc308e613d65048ae3ee4c31c6d149e83ba3e0..d21c4324d9eac75d718f34687d60bacd1fa83ce0 100644
--- a/spec/support/filtered_search_helpers.rb
+++ b/spec/support/filtered_search_helpers.rb
@@ -14,6 +14,9 @@ module FilteredSearchHelpers
     filtered_search.set(search)
 
     if submit
+      # Wait for the lazy author/assignee tokens that
+      # swap out the username with an avatar and name
+      wait_for_requests
       filtered_search.send_keys(:enter)
     end
   end
diff --git a/spec/support/generate-seed-repo-rb b/spec/support/generate-seed-repo-rb
index 7335f74c0e95799777e9b671b5cbeb740394db1d..c89389b90ca15c52465dcb091d837af41ffc5bf8 100755
--- a/spec/support/generate-seed-repo-rb
+++ b/spec/support/generate-seed-repo-rb
@@ -15,7 +15,7 @@
 require 'erb'
 require 'tempfile'
 
-SOURCE = 'https://gitlab.com/gitlab-org/gitlab-git-test.git'.freeze
+SOURCE = File.expand_path('../gitlab-git-test.git', __FILE__).freeze
 SCRIPT_NAME = 'generate-seed-repo-rb'.freeze
 REPO_NAME = 'gitlab-git-test.git'.freeze
 
diff --git a/spec/support/gitlab-git-test.git/HEAD b/spec/support/gitlab-git-test.git/HEAD
new file mode 100644
index 0000000000000000000000000000000000000000..cb089cd89a7d7686d284d8761201649346b5aa1c
--- /dev/null
+++ b/spec/support/gitlab-git-test.git/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/spec/support/gitlab-git-test.git/README.md b/spec/support/gitlab-git-test.git/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..f072cd421be1564441baf354653d54465dc113c4
--- /dev/null
+++ b/spec/support/gitlab-git-test.git/README.md
@@ -0,0 +1,16 @@
+# Gitlab::Git test repository
+
+This repository is used by (some of) the tests in spec/lib/gitlab/git.
+
+Do not add new large files to this repository. Otherwise we needlessly
+inflate the size of the gitlab-ce repository.
+
+## How to make changes to this repository
+
+- (if needed) clone `https://gitlab.com/gitlab-org/gitlab-ce.git` to your local machine
+- clone `gitlab-ce/spec/support/gitlab-git-test.git` locally (i.e. clone from your hard drive, not from the internet)
+- make changes in your local clone of gitlab-git-test
+- run `git push` which will push to your local source `gitlab-ce/spec/support/gitlab-git-test.git`
+- in gitlab-ce: run `spec/support/prepare-gitlab-git-test-for-commit`
+- in gitlab-ce: `git add spec/support/seed_repo.rb spec/support/gitlab-git-test.git`
+- commit your changes in gitlab-ce
diff --git a/spec/support/gitlab-git-test.git/config b/spec/support/gitlab-git-test.git/config
new file mode 100644
index 0000000000000000000000000000000000000000..03e2d1b1e0f812fd11ff3b621b05b66de86c2c2d
--- /dev/null
+++ b/spec/support/gitlab-git-test.git/config
@@ -0,0 +1,7 @@
+[core]
+	repositoryformatversion = 0
+	filemode = true
+	bare = true
+	precomposeunicode = true
+[remote "origin"]
+	url = https://gitlab.com/gitlab-org/gitlab-git-test.git
diff --git a/spec/support/gitlab-git-test.git/objects/pack/pack-691247af2a6acb0b63b73ac0cb90540e93614043.idx b/spec/support/gitlab-git-test.git/objects/pack/pack-691247af2a6acb0b63b73ac0cb90540e93614043.idx
new file mode 100644
index 0000000000000000000000000000000000000000..2253da798c44f667eba83c7d4cfd7cd34838200b
Binary files /dev/null and b/spec/support/gitlab-git-test.git/objects/pack/pack-691247af2a6acb0b63b73ac0cb90540e93614043.idx differ
diff --git a/spec/support/gitlab-git-test.git/objects/pack/pack-691247af2a6acb0b63b73ac0cb90540e93614043.pack b/spec/support/gitlab-git-test.git/objects/pack/pack-691247af2a6acb0b63b73ac0cb90540e93614043.pack
new file mode 100644
index 0000000000000000000000000000000000000000..3a61107c5b16f7ff2a54e7ba9339e9118cf468a0
Binary files /dev/null and b/spec/support/gitlab-git-test.git/objects/pack/pack-691247af2a6acb0b63b73ac0cb90540e93614043.pack differ
diff --git a/spec/support/gitlab-git-test.git/packed-refs b/spec/support/gitlab-git-test.git/packed-refs
new file mode 100644
index 0000000000000000000000000000000000000000..ce5ab1f705b3e1de2a029e5943b36c7a9cf00995
--- /dev/null
+++ b/spec/support/gitlab-git-test.git/packed-refs
@@ -0,0 +1,18 @@
+# pack-refs with: peeled fully-peeled 
+0b4bc9a49b562e85de7cc9e834518ea6828729b9 refs/heads/feature
+12d65c8dd2b2676fa3ac47d955accc085a37a9c1 refs/heads/fix
+6473c90867124755509e100d0d35ebdc85a0b6ae refs/heads/fix-blob-path
+58fa1a3af4de73ea83fe25a1ef1db8e0c56f67e5 refs/heads/fix-existing-submodule-dir
+40f4a7a617393735a95a0bb67b08385bc1e7c66d refs/heads/fix-mode
+9abd6a8c113a2dd76df3fdb3d58a8cec6db75f8d refs/heads/gitattributes
+46e1395e609395de004cacd4b142865ab0e52a29 refs/heads/gitattributes-updated
+4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6 refs/heads/master
+5937ac0a7beb003549fc5fd26fc247adbce4a52e refs/heads/merge-test
+f4e6814c3e4e7a0de82a9e7cd20c626cc963a2f8 refs/tags/v1.0.0
+^6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b refs/tags/v1.1.0
+^5937ac0a7beb003549fc5fd26fc247adbce4a52e
+10d64eed7760f2811ee2d64b44f1f7d3b364f17b refs/tags/v1.2.0
+^eb49186cfa5c4338011f5f590fac11bd66c5c631
+2ac1f24e253e08135507d0830508febaaccf02ee refs/tags/v1.2.1
+^fa1b1e6c004a68b7d8763b86455da9e6b23e36d6
diff --git a/spec/support/gitlab-git-test.git/refs/heads/.gitkeep b/spec/support/gitlab-git-test.git/refs/heads/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/spec/support/gitlab-git-test.git/refs/tags/.gitkeep b/spec/support/gitlab-git-test.git/refs/tags/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/spec/support/issue_helpers.rb b/spec/support/issue_helpers.rb
index 8524179374334dc15ab84ea4243c044877718d16..ffd72515f37ea4eb4164eec641e7adee72ef0d33 100644
--- a/spec/support/issue_helpers.rb
+++ b/spec/support/issue_helpers.rb
@@ -1,6 +1,6 @@
 module IssueHelpers
   def visit_issues(project, opts = {})
-    visit namespace_project_issues_path project.namespace, project, opts
+    visit project_issues_path project, opts
   end
 
   def first_issue
diff --git a/spec/support/issue_tracker_service_shared_example.rb b/spec/support/issue_tracker_service_shared_example.rb
index e70b3963d9d068dbf3598a71e054d19d24232aa3..a6ab03cb808f8c747972d19101eb587e7d93ea44 100644
--- a/spec/support/issue_tracker_service_shared_example.rb
+++ b/spec/support/issue_tracker_service_shared_example.rb
@@ -8,15 +8,15 @@ end
 
 RSpec.shared_examples 'allows project key on reference pattern' do |url_attr|
   it 'allows underscores in the project name' do
-    expect(subject.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234'
+    expect(described_class.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234'
   end
 
   it 'allows numbers in the project name' do
-    expect(subject.reference_pattern.match('EXT3_EXT-1234')[0]).to eq 'EXT3_EXT-1234'
+    expect(described_class.reference_pattern.match('EXT3_EXT-1234')[0]).to eq 'EXT3_EXT-1234'
   end
 
   it 'requires the project name to begin with A-Z' do
-    expect(subject.reference_pattern.match('3EXT_EXT-1234')).to eq nil
-    expect(subject.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234'
+    expect(described_class.reference_pattern.match('3EXT_EXT-1234')).to eq nil
+    expect(described_class.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234'
   end
 end
diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb
index e6da852e7288aa09b0a4bd759254c66c8acc921c..4c88958264bb8cacbeef7a75ed0f161370797047 100644
--- a/spec/support/login_helpers.rb
+++ b/spec/support/login_helpers.rb
@@ -6,15 +6,15 @@ module LoginHelpers
   # Examples:
   #
   #   # Create a user automatically
-  #   login_as(:user)
+  #   gitlab_sign_in(:user)
   #
   #   # Create an admin automatically
-  #   login_as(:admin)
+  #   gitlab_sign_in(:admin)
   #
   #   # Provide an existing User record
   #   user = create(:user)
-  #   login_as(user)
-  def login_as(user_or_role)
+  #   gitlab_sign_in(user)
+  def gitlab_sign_in(user_or_role, **kwargs)
     @user =
       if user_or_role.is_a?(User)
         user_or_role
@@ -22,26 +22,44 @@ module LoginHelpers
         create(user_or_role)
       end
 
-    login_with(@user)
+    gitlab_sign_in_with(@user, **kwargs)
   end
 
-  # Internal: Login as the specified user
+  def gitlab_sign_in_via(provider, user, uid)
+    mock_auth_hash(provider, uid, user.email)
+    visit new_user_session_path
+    click_link provider
+  end
+
+  # Requires Javascript driver.
+  def gitlab_sign_out
+    find(".header-user-dropdown-toggle").click
+    click_link "Sign out"
+    # check the sign_in button
+    expect(page).to have_button('Sign in')
+  end
+
+  # Logout without JavaScript driver
+  def gitlab_sign_out_direct
+    page.driver.submit :delete, '/users/sign_out', {}
+  end
+
+  private
+
+  # Private: Login as the specified user
   #
   # user     - User instance to login with
   # remember - Whether or not to check "Remember me" (default: false)
-  def login_with(user, remember: false)
+  def gitlab_sign_in_with(user, remember: false)
     visit new_user_session_path
+
     fill_in "user_login", with: user.email
     fill_in "user_password", with: "12345678"
     check 'user_remember_me' if remember
+
     click_button "Sign in"
-    Thread.current[:current_user] = user
-  end
 
-  def login_via(provider, user, uid)
-    mock_auth_hash(provider, uid, user.email)
-    visit new_user_session_path
-    click_link provider
+    Thread.current[:current_user] = user
   end
 
   def mock_auth_hash(provider, uid, email)
@@ -72,16 +90,24 @@ module LoginHelpers
     Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[:saml]
   end
 
-  # Requires Javascript driver.
-  def logout
-    find(".header-user-dropdown-toggle").click
-    click_link "Sign out"
-    # check the sign_in button
-    expect(page).to have_button('Sign in')
+  def mock_saml_config
+    OpenStruct.new(name: 'saml', label: 'saml', args: {
+      assertion_consumer_service_url: 'https://localhost:3443/users/auth/saml/callback',
+      idp_cert_fingerprint: '26:43:2C:47:AF:F0:6B:D0:07:9C:AD:A3:74:FE:5D:94:5F:4E:9E:52',
+      idp_sso_target_url: 'https://idp.example.com/sso/saml',
+      issuer: 'https://localhost:3443/',
+      name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
+    })
   end
 
-  # Logout without JavaScript driver
-  def logout_direct
-    page.driver.submit :delete, '/users/sign_out', {}
+  def stub_omniauth_saml_config(messages)
+    Rails.application.env_config['devise.mapping'] = Devise.mappings[:user]
+    Rails.application.routes.disable_clear_and_finalize = true
+    Rails.application.routes.draw do
+      post '/users/auth/saml' => 'omniauth_callbacks#saml'
+    end
+    allow(Gitlab::OAuth::Provider).to receive_messages(providers: [:saml], config_for: mock_saml_config)
+    stub_omniauth_setting(messages)
+    expect_any_instance_of(Object).to receive(:omniauth_authorize_path).with(:user, "saml").and_return('/users/auth/saml')
   end
 end
diff --git a/spec/support/matchers/access_matchers_for_controller.rb b/spec/support/matchers/access_matchers_for_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fb43f51c70c48b84fce249add51fa75cefc1f1e5
--- /dev/null
+++ b/spec/support/matchers/access_matchers_for_controller.rb
@@ -0,0 +1,84 @@
+# AccessMatchersForController
+#
+# For testing authorize_xxx in controller. 
+module AccessMatchersForController
+  extend RSpec::Matchers::DSL
+  include Warden::Test::Helpers
+
+  EXPECTED_STATUS_CODE_ALLOWED = [200, 201, 302].freeze
+  EXPECTED_STATUS_CODE_DENIED = [401, 404].freeze
+
+  def emulate_user(role, membership = nil)
+    case role
+    when :admin
+      user = create(:admin)
+      sign_in(user)
+    when :user
+      user = create(:user)
+      sign_in(user)
+    when :external
+      user = create(:user, external: true)
+      sign_in(user)
+    when :visitor
+      user = nil
+    when User
+      user = role
+      sign_in(user)
+    when *Gitlab::Access.sym_options_with_owner.keys # owner, master, developer, reporter, guest
+      raise ArgumentError, "cannot emulate #{role} without membership parent" unless membership
+
+      user = create_user_by_membership(role, membership)
+      sign_in(user)
+    else
+      raise ArgumentError, "cannot emulate user #{role}"
+    end
+
+    user
+  end
+
+  def create_user_by_membership(role, membership)
+    if role == :owner && membership.owner
+      user = membership.owner
+    else
+      user = create(:user)
+      membership.public_send(:"add_#{role}", user)
+    end
+    user
+  end
+
+  def description_for(role, type, expected, result)
+    "be #{type} for #{role}. Expected: #{expected.join(',')} Got: #{result}"
+  end
+
+  matcher :be_allowed_for do |role|
+    match do |action|
+      emulate_user(role, @membership)
+      action.call
+
+      EXPECTED_STATUS_CODE_ALLOWED.include?(response.status)
+    end
+
+    chain :of do |membership|
+      @membership = membership
+    end
+
+    description { description_for(role, 'allowed', EXPECTED_STATUS_CODE_ALLOWED, response.status) }
+    supports_block_expectations
+  end
+
+  matcher :be_denied_for do |role|
+    match do |action|
+      emulate_user(role, @membership)
+      action.call
+
+      EXPECTED_STATUS_CODE_DENIED.include?(response.status)
+    end
+
+    chain :of do |membership|
+      @membership = membership
+    end
+
+    description { description_for(role, 'denied', EXPECTED_STATUS_CODE_DENIED, response.status) }
+    supports_block_expectations
+  end
+end
diff --git a/spec/support/matchers/be_utf8.rb b/spec/support/matchers/be_utf8.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ea806352422ba00294f24b2252cda9dfba802a7e
--- /dev/null
+++ b/spec/support/matchers/be_utf8.rb
@@ -0,0 +1,9 @@
+RSpec::Matchers.define :be_utf8 do |_|
+  match do |actual|
+    actual.is_a?(String) && actual.encoding == Encoding.find('UTF-8')
+  end
+
+  description do
+    "be a String with encoding UTF-8"
+  end
+end
diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb
index 87936bb485941ee0295659aa6f4234b0fe5c8571..3ac201f1fb195f80384b22ee1677597a5f709792 100644
--- a/spec/support/mentionable_shared_examples.rb
+++ b/spec/support/mentionable_shared_examples.rb
@@ -81,8 +81,8 @@ shared_examples 'a mentionable' do
                          ext_issue, ext_mr, ext_commit]
 
     mentioned_objects.each do |referenced|
-      expect(SystemNoteService).to receive(:cross_reference).
-        with(referenced, subject.local_reference, author)
+      expect(SystemNoteService).to receive(:cross_reference)
+        .with(referenced, subject.local_reference, author)
     end
 
     subject.create_cross_references!
@@ -127,15 +127,15 @@ shared_examples 'an editable mentionable' do
     # These three objects were already referenced, and should not receive new
     # notes
     [mentioned_issue, mentioned_commit, ext_issue].each do |oldref|
-      expect(SystemNoteService).not_to receive(:cross_reference).
-        with(oldref, any_args)
+      expect(SystemNoteService).not_to receive(:cross_reference)
+        .with(oldref, any_args)
     end
 
     # These two issues are new and should receive reference notes
     # In the case of MergeRequests remember that cannot mention commits included in the MergeRequest
     new_issues.each do |newref|
-      expect(SystemNoteService).to receive(:cross_reference).
-        with(newref, subject.local_reference, author)
+      expect(SystemNoteService).to receive(:cross_reference)
+        .with(newref, subject.local_reference, author)
     end
 
     set_mentionable_text.call(new_text)
diff --git a/spec/support/merge_request_helpers.rb b/spec/support/merge_request_helpers.rb
index 326b85eabd01ab5a300c66256e54bf5ac8b0987b..772adff4626c941b9657c8b7a293e56472c775ad 100644
--- a/spec/support/merge_request_helpers.rb
+++ b/spec/support/merge_request_helpers.rb
@@ -1,6 +1,6 @@
 module MergeRequestHelpers
   def visit_merge_requests(project, opts = {})
-    visit namespace_project_merge_requests_path project.namespace, project, opts
+    visit project_merge_requests_path project, opts
   end
 
   def first_merge_request
diff --git a/spec/support/prepare-gitlab-git-test-for-commit b/spec/support/prepare-gitlab-git-test-for-commit
new file mode 100755
index 0000000000000000000000000000000000000000..3047786a599cb25ab9592512593b2c8b78865aad
--- /dev/null
+++ b/spec/support/prepare-gitlab-git-test-for-commit
@@ -0,0 +1,17 @@
+#!/usr/bin/env ruby
+
+abort unless [
+  system('spec/support/generate-seed-repo-rb', out: 'spec/support/seed_repo.rb'),
+  system('spec/support/unpack-gitlab-git-test')
+].all?
+
+exit if ARGV.first != '--check-for-changes'
+
+git_status = IO.popen(%w[git status --porcelain], &:read)
+abort unless $?.success?
+
+puts git_status
+
+if git_status.lines.grep(%r{^.. spec/support/gitlab-git-test.git}).any?
+  abort "error: detected changes in gitlab-git-test.git"
+end
diff --git a/spec/support/project_features_apply_to_issuables_shared_examples.rb b/spec/support/project_features_apply_to_issuables_shared_examples.rb
index f8b7d0527ba5f7dd459fc87b70fb621aa24aca89..81b51509e0b05e89cc914b48bfee588dc4ee1cd4 100644
--- a/spec/support/project_features_apply_to_issuables_shared_examples.rb
+++ b/spec/support/project_features_apply_to_issuables_shared_examples.rb
@@ -18,7 +18,7 @@ shared_examples 'project features apply to issuables' do |klass|
 
   before do
     _ = issuable
-    login_as(user) if user
+    gitlab_sign_in(user) if user
     visit path
   end
 
diff --git a/spec/support/prometheus/additional_metrics_shared_examples.rb b/spec/support/prometheus/additional_metrics_shared_examples.rb
new file mode 100644
index 0000000000000000000000000000000000000000..016e16fc8d426040128778a27d6f4de553a81904
--- /dev/null
+++ b/spec/support/prometheus/additional_metrics_shared_examples.rb
@@ -0,0 +1,101 @@
+RSpec.shared_examples 'additional metrics query' do
+  include Prometheus::MetricBuilders
+
+  let(:metric_group_class) { Gitlab::Prometheus::MetricGroup }
+  let(:metric_class) { Gitlab::Prometheus::Metric }
+
+  let(:metric_names) { %w{metric_a metric_b} }
+
+  let(:query_range_result) do
+    [{ 'metric': {}, 'values': [[1488758662.506, '0.00002996364761904785'], [1488758722.506, '0.00003090239047619091']] }]
+  end
+
+  before do
+    allow(client).to receive(:label_values).and_return(metric_names)
+    allow(metric_group_class).to receive(:all).and_return([simple_metric_group(metrics: [simple_metric])])
+  end
+
+  context 'with one group where two metrics is found' do
+    before do
+      allow(metric_group_class).to receive(:all).and_return([simple_metric_group])
+    end
+
+    context 'some queries return results' do
+      before do
+        allow(client).to receive(:query_range).with('query_range_a', any_args).and_return(query_range_result)
+        allow(client).to receive(:query_range).with('query_range_b', any_args).and_return(query_range_result)
+        allow(client).to receive(:query_range).with('query_range_empty', any_args).and_return([])
+      end
+
+      it 'return group data only for queries with results' do
+        expected = [
+          {
+            group: 'name',
+            priority: 1,
+            metrics: [
+              {
+                title: 'title', weight: 1, y_label: 'Values', queries: [
+                { query_range: 'query_range_a', result: query_range_result },
+                { query_range: 'query_range_b', label: 'label', unit: 'unit', result: query_range_result }
+              ]
+              }
+            ]
+          }
+        ]
+
+        expect(query_result).to match_schema('prometheus/additional_metrics_query_result')
+        expect(query_result).to eq(expected)
+      end
+    end
+  end
+
+  context 'with two groups with one metric each' do
+    let(:metrics) { [simple_metric(queries: [simple_query])] }
+    before do
+      allow(metric_group_class).to receive(:all).and_return(
+        [
+          simple_metric_group(name: 'group_a', metrics: [simple_metric(queries: [simple_query])]),
+          simple_metric_group(name: 'group_b', metrics: [simple_metric(title: 'title_b', queries: [simple_query('b')])])
+        ])
+      allow(client).to receive(:label_values).and_return(metric_names)
+    end
+
+    context 'both queries return results' do
+      before do
+        allow(client).to receive(:query_range).with('query_range_a', any_args).and_return(query_range_result)
+        allow(client).to receive(:query_range).with('query_range_b', any_args).and_return(query_range_result)
+      end
+
+      it 'return group data both queries' do
+        queries_with_result_a = { queries: [{ query_range: 'query_range_a', result: query_range_result }] }
+        queries_with_result_b = { queries: [{ query_range: 'query_range_b', result: query_range_result }] }
+
+        expect(query_result).to match_schema('prometheus/additional_metrics_query_result')
+
+        expect(query_result.count).to eq(2)
+        expect(query_result).to all(satisfy { |r| r[:metrics].count == 1 })
+
+        expect(query_result[0][:metrics].first).to include(queries_with_result_a)
+        expect(query_result[1][:metrics].first).to include(queries_with_result_b)
+      end
+    end
+
+    context 'one query returns result' do
+      before do
+        allow(client).to receive(:query_range).with('query_range_a', any_args).and_return(query_range_result)
+        allow(client).to receive(:query_range).with('query_range_b', any_args).and_return([])
+      end
+
+      it 'return group data only for query with results' do
+        queries_with_result = { queries: [{ query_range: 'query_range_a', result: query_range_result }] }
+
+        expect(query_result).to match_schema('prometheus/additional_metrics_query_result')
+
+        expect(query_result.count).to eq(1)
+        expect(query_result).to all(satisfy { |r| r[:metrics].count == 1 })
+
+        expect(query_result.first[:metrics].first).to include(queries_with_result)
+      end
+    end
+  end
+end
diff --git a/spec/support/prometheus/metric_builders.rb b/spec/support/prometheus/metric_builders.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c8d056d3fc882cfd16d054d8184b8895d4accbab
--- /dev/null
+++ b/spec/support/prometheus/metric_builders.rb
@@ -0,0 +1,27 @@
+module Prometheus
+  module MetricBuilders
+    def simple_query(suffix = 'a', **opts)
+      { query_range: "query_range_#{suffix}" }.merge(opts)
+    end
+
+    def simple_queries
+      [simple_query, simple_query('b', label: 'label', unit: 'unit')]
+    end
+
+    def simple_metric(title: 'title', required_metrics: [], queries: [simple_query])
+      Gitlab::Prometheus::Metric.new(title: title, required_metrics: required_metrics, weight: 1, queries: queries)
+    end
+
+    def simple_metrics(added_metric_name: 'metric_a')
+      [
+        simple_metric(required_metrics: %W(#{added_metric_name} metric_b), queries: simple_queries),
+        simple_metric(required_metrics: [added_metric_name], queries: [simple_query('empty')]),
+        simple_metric(required_metrics: %w{metric_c})
+      ]
+    end
+
+    def simple_metric_group(name: 'name', metrics: simple_metrics)
+      Gitlab::Prometheus::MetricGroup.new(name: name, priority: 1, metrics: metrics)
+    end
+  end
+end
diff --git a/spec/support/prometheus_helpers.rb b/spec/support/prometheus_helpers.rb
index 6b9ebcf2bb3beddd9ee00eb3c1bbb9e20875dae2..4212be2cc88c7a6d8b3780fe5848775f256ffb6d 100644
--- a/spec/support/prometheus_helpers.rb
+++ b/spec/support/prometheus_helpers.rb
@@ -36,6 +36,19 @@ module PrometheusHelpers
     "https://prometheus.example.com/api/v1/query_range?#{query}"
   end
 
+  def prometheus_label_values_url(name)
+    "https://prometheus.example.com/api/v1/label/#{name}/values"
+  end
+
+  def prometheus_series_url(*matches, start: 8.hours.ago, stop: Time.now)
+    query = {
+      match: matches,
+      start: start.to_f,
+      end: stop.to_f
+    }.to_query
+    "https://prometheus.example.com/api/v1/series?#{query}"
+  end
+
   def stub_prometheus_request(url, body: {}, status: 200)
     WebMock.stub_request(:get, url)
       .to_return({
@@ -83,6 +96,19 @@ module PrometheusHelpers
   end
 
   def prometheus_data(last_update: Time.now.utc)
+    {
+      success: true,
+      data: {
+        memory_values: prometheus_values_body('matrix').dig(:data, :result),
+        memory_current: prometheus_value_body('vector').dig(:data, :result),
+        cpu_values: prometheus_values_body('matrix').dig(:data, :result),
+        cpu_current: prometheus_value_body('vector').dig(:data, :result)
+      },
+      last_update: last_update
+    }
+  end
+
+  def prometheus_metrics_data(last_update: Time.now.utc)
     {
       success: true,
       metrics: {
@@ -140,4 +166,37 @@ module PrometheusHelpers
       }
     }
   end
+
+  def prometheus_label_values
+    {
+      'status': 'success',
+      'data': %w(job_adds job_controller_rate_limiter_use job_depth job_queue_latency job_work_duration_sum up)
+    }
+  end
+
+  def prometheus_series(name)
+    {
+      'status': 'success',
+      'data': [
+        {
+          '__name__': name,
+          'container_name': 'gitlab',
+          'environment': 'mattermost',
+          'id': '/docker/9953982f95cf5010dfc59d7864564d5f188aaecddeda343699783009f89db667',
+          'image': 'gitlab/gitlab-ce:8.15.4-ce.1',
+          'instance': 'minikube',
+          'job': 'kubernetes-nodes',
+          'name': 'k8s_gitlab.e6611886_mattermost-4210310111-77z8r_gitlab_2298ae6b-da24-11e6-baee-8e7f67d0eb3a_43536cb6',
+          'namespace': 'gitlab',
+          'pod_name': 'mattermost-4210310111-77z8r'
+        },
+        {
+          '__name__': name,
+          'id': '/docker',
+          'instance': 'minikube',
+          'job': 'kubernetes-nodes'
+        }
+      ]
+    }
+  end
 end
diff --git a/spec/support/protected_tags/access_control_ce_shared_examples.rb b/spec/support/protected_tags/access_control_ce_shared_examples.rb
index 1d11512ef82e06da41a2464c4c1a8b9e162061a4..421a51fc3360a72b573936d98f99503ab0769518 100644
--- a/spec/support/protected_tags/access_control_ce_shared_examples.rb
+++ b/spec/support/protected_tags/access_control_ce_shared_examples.rb
@@ -1,7 +1,7 @@
 RSpec.shared_examples "protected tags > access control > CE" do
   ProtectedTag::CreateAccessLevel.human_access_levels.each do |(access_type_id, access_type_name)|
     it "allows creating protected tags that #{access_type_name} can create" do
-      visit namespace_project_protected_tags_path(project.namespace, project)
+      visit project_protected_tags_path(project)
 
       set_protected_tag_name('master')
 
@@ -22,7 +22,7 @@ RSpec.shared_examples "protected tags > access control > CE" do
     end
 
     it "allows updating protected tags so that #{access_type_name} can create them" do
-      visit namespace_project_protected_tags_path(project.namespace, project)
+      visit project_protected_tags_path(project)
 
       set_protected_tag_name('master')
 
diff --git a/spec/support/slash_commands_helpers.rb b/spec/support/quick_actions_helpers.rb
similarity index 88%
rename from spec/support/slash_commands_helpers.rb
rename to spec/support/quick_actions_helpers.rb
index 4bfe481115fa2df6079972c058715a7fc91648c5..d2aaae7518f8c28d678ab17c2f383068c69aae27 100644
--- a/spec/support/slash_commands_helpers.rb
+++ b/spec/support/quick_actions_helpers.rb
@@ -1,4 +1,4 @@
-module SlashCommandsHelpers
+module QuickActionsHelpers
   def write_note(text)
     Sidekiq::Testing.fake! do
       page.within('.js-main-target-form') do
diff --git a/spec/support/reactive_caching_helpers.rb b/spec/support/reactive_caching_helpers.rb
index 98eb57f8b54b97352b852ad5310502c4aa5ba193..34124f02133328ad2e48b2025b5dd255b842b60c 100644
--- a/spec/support/reactive_caching_helpers.rb
+++ b/spec/support/reactive_caching_helpers.rb
@@ -35,8 +35,8 @@ module ReactiveCachingHelpers
   end
 
   def expect_reactive_cache_update_queued(subject)
-    expect(ReactiveCachingWorker).
-      to receive(:perform_in).
-      with(subject.class.reactive_cache_refresh_interval, subject.class, subject.id)
+    expect(ReactiveCachingWorker)
+      .to receive(:perform_in)
+      .with(subject.class.reactive_cache_refresh_interval, subject.class, subject.id)
   end
 end
diff --git a/spec/support/routing_helpers.rb b/spec/support/routing_helpers.rb
new file mode 100644
index 0000000000000000000000000000000000000000..af1f4760804d0e50f5ee41bb40576e53af1861f1
--- /dev/null
+++ b/spec/support/routing_helpers.rb
@@ -0,0 +1,3 @@
+RSpec.configure do |config|
+  config.include GitlabRoutingHelper
+end
diff --git a/spec/support/seed_helper.rb b/spec/support/seed_helper.rb
index 47b5f556e66ca08c363834b022ba891f3bef72dc..8731847592bef17aa2f54e26fffef07fb6cbe6aa 100644
--- a/spec/support/seed_helper.rb
+++ b/spec/support/seed_helper.rb
@@ -9,7 +9,7 @@ TEST_MUTABLE_REPO_PATH = 'mutable-repo.git'.freeze
 TEST_BROKEN_REPO_PATH  = 'broken-repo.git'.freeze
 
 module SeedHelper
-  GITLAB_GIT_TEST_REPO_URL = ENV.fetch('GITLAB_GIT_TEST_REPO_URL', 'https://gitlab.com/gitlab-org/gitlab-git-test.git').freeze
+  GITLAB_GIT_TEST_REPO_URL = File.expand_path('../gitlab-git-test.git', __FILE__).freeze
 
   def ensure_seeds
     if File.exist?(SEED_STORAGE_PATH)
diff --git a/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb b/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb
index 5e6f9f323a1dfb67a6839bc64414bfd7ba98a8eb..9399745f9003697761c4882d83c504623990073f 100644
--- a/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb
+++ b/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb
@@ -1,7 +1,7 @@
 # Specifications for behavior common to all objects with executable attributes.
 # It can take a `default_params`.
 
-shared_examples 'new issuable record that supports slash commands' do
+shared_examples 'new issuable record that supports quick actions' do
   let!(:project) { create(:project, :repository) }
   let(:user) { create(:user).tap { |u| project.team << [u, :master] } }
   let(:assignee) { create(:user) }
diff --git a/spec/support/services_shared_context.rb b/spec/support/services_shared_context.rb
index 66c93890e31ef202211c7d1643f206f8e7567ddf..7457484a932fa4d3d8e6ff245af525ff55d97e02 100644
--- a/spec/support/services_shared_context.rb
+++ b/spec/support/services_shared_context.rb
@@ -6,9 +6,9 @@ Service.available_services_names.each do |service|
     let(:service_fields) { service_klass.new.fields }
     let(:service_attrs_list) { service_fields.inject([]) {|arr, hash| arr << hash[:name].to_sym } }
     let(:service_attrs_list_without_passwords) do
-      service_fields.
-        select { |field| field[:type] != 'password' }.
-        map { |field| field[:name].to_sym}
+      service_fields
+        .select { |field| field[:type] != 'password' }
+        .map { |field| field[:name].to_sym}
     end
     let(:service_attrs) do
       service_attrs_list.inject({}) do |hash, k|
diff --git a/spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb b/spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb
new file mode 100644
index 0000000000000000000000000000000000000000..96c821b26f72deb94b82b48a7276fc5189de52dd
--- /dev/null
+++ b/spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb
@@ -0,0 +1,9 @@
+shared_examples 'issue sidebar stays collapsed on mobile' do
+  before do
+    resize_screen_xs
+  end
+
+  it 'keeps the sidebar collapsed' do
+    expect(page).not_to have_css('.right-sidebar.right-sidebar-collapsed')
+  end
+end
diff --git a/spec/support/protected_branches/access_control_ce_shared_examples.rb b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb
similarity index 87%
rename from spec/support/protected_branches/access_control_ce_shared_examples.rb
rename to spec/support/shared_examples/features/protected_branches_access_control_ce.rb
index 287d6bb13c35309c97d0da66d1b5761401a17d05..66e598e2691a6b5b34fccdaae421cdae761a1218 100644
--- a/spec/support/protected_branches/access_control_ce_shared_examples.rb
+++ b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb
@@ -1,7 +1,7 @@
-RSpec.shared_examples "protected branches > access control > CE" do
+shared_examples "protected branches > access control > CE" do
   ProtectedBranch::PushAccessLevel.human_access_levels.each do |(access_type_id, access_type_name)|
     it "allows creating protected branches that #{access_type_name} can push to" do
-      visit namespace_project_protected_branches_path(project.namespace, project)
+      visit project_protected_branches_path(project)
 
       set_protected_branch_name('master')
 
@@ -21,7 +21,7 @@ RSpec.shared_examples "protected branches > access control > CE" do
     end
 
     it "allows updating protected branches so that #{access_type_name} can push to them" do
-      visit namespace_project_protected_branches_path(project.namespace, project)
+      visit project_protected_branches_path(project)
 
       set_protected_branch_name('master')
 
@@ -46,7 +46,7 @@ RSpec.shared_examples "protected branches > access control > CE" do
 
   ProtectedBranch::MergeAccessLevel.human_access_levels.each do |(access_type_id, access_type_name)|
     it "allows creating protected branches that #{access_type_name} can merge to" do
-      visit namespace_project_protected_branches_path(project.namespace, project)
+      visit project_protected_branches_path(project)
 
       set_protected_branch_name('master')
 
@@ -66,7 +66,7 @@ RSpec.shared_examples "protected branches > access control > CE" do
     end
 
     it "allows updating protected branches so that #{access_type_name} can merge to them" do
-      visit namespace_project_protected_branches_path(project.namespace, project)
+      visit project_protected_branches_path(project)
 
       set_protected_branch_name('master')
 
diff --git a/spec/support/slack_mattermost_notifications_shared_examples.rb b/spec/support/slack_mattermost_notifications_shared_examples.rb
index a7deb038703a9823b9aca54cfbad8bcbfc0186b1..044c09d5fde8b3fcc1466d8af9ebf8522f320765 100644
--- a/spec/support/slack_mattermost_notifications_shared_examples.rb
+++ b/spec/support/slack_mattermost_notifications_shared_examples.rb
@@ -108,9 +108,9 @@ RSpec.shared_examples 'slack or mattermost notifications' do
     it 'uses the username as an option for slack when configured' do
       allow(chat_service).to receive(:username).and_return(username)
 
-      expect(Slack::Notifier).to receive(:new).
-       with(webhook_url, username: username).
-       and_return(
+      expect(Slack::Notifier).to receive(:new)
+       .with(webhook_url, username: username)
+       .and_return(
          double(:slack_service).as_null_object
        )
 
@@ -119,9 +119,9 @@ RSpec.shared_examples 'slack or mattermost notifications' do
 
     it 'uses the channel as an option when it is configured' do
       allow(chat_service).to receive(:channel).and_return(channel)
-      expect(Slack::Notifier).to receive(:new).
-        with(webhook_url, channel: channel).
-        and_return(
+      expect(Slack::Notifier).to receive(:new)
+        .with(webhook_url, channel: channel)
+        .and_return(
           double(:slack_service).as_null_object
         )
       chat_service.execute(push_sample_data)
@@ -131,9 +131,9 @@ RSpec.shared_examples 'slack or mattermost notifications' do
       it "uses the right channel for push event" do
         chat_service.update_attributes(push_channel: "random")
 
-        expect(Slack::Notifier).to receive(:new).
-         with(webhook_url, channel: "random").
-         and_return(
+        expect(Slack::Notifier).to receive(:new)
+         .with(webhook_url, channel: "random")
+         .and_return(
            double(:slack_service).as_null_object
          )
 
@@ -143,9 +143,9 @@ RSpec.shared_examples 'slack or mattermost notifications' do
       it "uses the right channel for merge request event" do
         chat_service.update_attributes(merge_request_channel: "random")
 
-        expect(Slack::Notifier).to receive(:new).
-         with(webhook_url, channel: "random").
-         and_return(
+        expect(Slack::Notifier).to receive(:new)
+         .with(webhook_url, channel: "random")
+         .and_return(
            double(:slack_service).as_null_object
          )
 
@@ -155,9 +155,9 @@ RSpec.shared_examples 'slack or mattermost notifications' do
       it "uses the right channel for issue event" do
         chat_service.update_attributes(issue_channel: "random")
 
-        expect(Slack::Notifier).to receive(:new).
-         with(webhook_url, channel: "random").
-         and_return(
+        expect(Slack::Notifier).to receive(:new)
+         .with(webhook_url, channel: "random")
+         .and_return(
            double(:slack_service).as_null_object
          )
 
@@ -167,9 +167,9 @@ RSpec.shared_examples 'slack or mattermost notifications' do
       it "uses the right channel for wiki event" do
         chat_service.update_attributes(wiki_page_channel: "random")
 
-        expect(Slack::Notifier).to receive(:new).
-         with(webhook_url, channel: "random").
-         and_return(
+        expect(Slack::Notifier).to receive(:new)
+         .with(webhook_url, channel: "random")
+         .and_return(
            double(:slack_service).as_null_object
          )
 
@@ -186,9 +186,9 @@ RSpec.shared_examples 'slack or mattermost notifications' do
 
           note_data = Gitlab::DataBuilder::Note.build(issue_note, user)
 
-          expect(Slack::Notifier).to receive(:new).
-           with(webhook_url, channel: "random").
-           and_return(
+          expect(Slack::Notifier).to receive(:new)
+           .with(webhook_url, channel: "random")
+           .and_return(
              double(:slack_service).as_null_object
            )
 
diff --git a/spec/support/stub_configuration.rb b/spec/support/stub_configuration.rb
index b39a23bd18a8f3bdd80ef73b0a23ca4374f77e57..48f454c71876b00eb9c9be967d877b383f163717 100644
--- a/spec/support/stub_configuration.rb
+++ b/spec/support/stub_configuration.rb
@@ -5,8 +5,8 @@ module StubConfiguration
     # Stubbing both of these because we're not yet consistent with how we access
     # current application settings
     allow_any_instance_of(ApplicationSetting).to receive_messages(messages)
-    allow(Gitlab::CurrentSettings.current_application_settings).
-      to receive_messages(messages)
+    allow(Gitlab::CurrentSettings.current_application_settings)
+      .to receive_messages(messages)
   end
 
   def stub_config_setting(messages)
diff --git a/spec/support/stub_env.rb b/spec/support/stub_env.rb
index 18597b5c71f7c481fc4bfef64c9494cb7f9452e7..b89288671745655b164b88ee77f2d95b453fc6a8 100644
--- a/spec/support/stub_env.rb
+++ b/spec/support/stub_env.rb
@@ -1,7 +1,33 @@
+# Inspired by https://github.com/ljkbennett/stub_env/blob/master/lib/stub_env/helpers.rb
 module StubENV
-  def stub_env(key, value)
-    allow(ENV).to receive(:[]).and_call_original unless @env_already_stubbed
-    @env_already_stubbed ||= true
+  def stub_env(key_or_hash, value = nil)
+    init_stub unless env_stubbed?
+    if key_or_hash.is_a? Hash
+      key_or_hash.each { |k, v| add_stubbed_value(k, v) }
+    else
+      add_stubbed_value key_or_hash, value
+    end
+  end
+
+  private
+
+  STUBBED_KEY = '__STUBBED__'.freeze
+
+  def add_stubbed_value(key, value)
     allow(ENV).to receive(:[]).with(key).and_return(value)
+    allow(ENV).to receive(:fetch).with(key).and_return(value)
+    allow(ENV).to receive(:fetch).with(key, anything()) do |_, default_val|
+      value || default_val
+    end
+  end
+
+  def env_stubbed?
+    ENV[STUBBED_KEY]
+  end
+
+  def init_stub
+    allow(ENV).to receive(:[]).and_call_original
+    allow(ENV).to receive(:fetch).and_call_original
+    add_stubbed_value(STUBBED_KEY, true)
   end
 end
diff --git a/spec/support/stub_gitlab_calls.rb b/spec/support/stub_gitlab_calls.rb
index ded2d5930597d2dedb0d95e8881604f0806b7b5e..78a2ff7374601f7d7864257a38d99e0e241af039 100644
--- a/spec/support/stub_gitlab_calls.rb
+++ b/spec/support/stub_gitlab_calls.rb
@@ -68,22 +68,22 @@ module StubGitlabCalls
   def stub_session
     f = File.read(Rails.root.join('spec/support/gitlab_stubs/session.json'))
 
-    stub_request(:post, "#{gitlab_url}api/v3/session.json").
-      with(body: "{\"email\":\"test@test.com\",\"password\":\"123456\"}",
-           headers: { 'Content-Type' => 'application/json' }).
-           to_return(status: 201, body: f, headers: { 'Content-Type' => 'application/json' })
+    stub_request(:post, "#{gitlab_url}api/v3/session.json")
+      .with(body: "{\"email\":\"test@test.com\",\"password\":\"123456\"}",
+            headers: { 'Content-Type' => 'application/json' })
+      .to_return(status: 201, body: f, headers: { 'Content-Type' => 'application/json' })
   end
 
   def stub_user
     f = File.read(Rails.root.join('spec/support/gitlab_stubs/user.json'))
 
-    stub_request(:get, "#{gitlab_url}api/v3/user?private_token=Wvjy2Krpb7y8xi93owUz").
-      with(headers: { 'Content-Type' => 'application/json' }).
-      to_return(status: 200, body: f, headers: { 'Content-Type' => 'application/json' })
+    stub_request(:get, "#{gitlab_url}api/v3/user?private_token=Wvjy2Krpb7y8xi93owUz")
+      .with(headers: { 'Content-Type' => 'application/json' })
+      .to_return(status: 200, body: f, headers: { 'Content-Type' => 'application/json' })
 
-    stub_request(:get, "#{gitlab_url}api/v3/user?access_token=some_token").
-      with(headers: { 'Content-Type' => 'application/json' }).
-      to_return(status: 200, body: f, headers: { 'Content-Type' => 'application/json' })
+    stub_request(:get, "#{gitlab_url}api/v3/user?access_token=some_token")
+      .with(headers: { 'Content-Type' => 'application/json' })
+      .to_return(status: 200, body: f, headers: { 'Content-Type' => 'application/json' })
   end
 
   def stub_project_8
@@ -99,21 +99,21 @@ module StubGitlabCalls
   def stub_projects
     f = File.read(Rails.root.join('spec/support/gitlab_stubs/projects.json'))
 
-    stub_request(:get, "#{gitlab_url}api/v3/projects.json?archived=false&ci_enabled_first=true&private_token=Wvjy2Krpb7y8xi93owUz").
-      with(headers: { 'Content-Type' => 'application/json' }).
-      to_return(status: 200, body: f, headers: { 'Content-Type' => 'application/json' })
+    stub_request(:get, "#{gitlab_url}api/v3/projects.json?archived=false&ci_enabled_first=true&private_token=Wvjy2Krpb7y8xi93owUz")
+      .with(headers: { 'Content-Type' => 'application/json' })
+      .to_return(status: 200, body: f, headers: { 'Content-Type' => 'application/json' })
   end
 
   def stub_projects_owned
-    stub_request(:get, "#{gitlab_url}api/v3/projects/owned.json?archived=false&ci_enabled_first=true&private_token=Wvjy2Krpb7y8xi93owUz").
-      with(headers: { 'Content-Type' => 'application/json' }).
-      to_return(status: 200, body: "", headers: {})
+    stub_request(:get, "#{gitlab_url}api/v3/projects/owned.json?archived=false&ci_enabled_first=true&private_token=Wvjy2Krpb7y8xi93owUz")
+      .with(headers: { 'Content-Type' => 'application/json' })
+      .to_return(status: 200, body: "", headers: {})
   end
 
   def stub_ci_enable
-    stub_request(:put, "#{gitlab_url}api/v3/projects/2/services/gitlab-ci.json?private_token=Wvjy2Krpb7y8xi93owUz").
-      with(headers: { 'Content-Type' => 'application/json' }).
-      to_return(status: 200, body: "", headers: {})
+    stub_request(:put, "#{gitlab_url}api/v3/projects/2/services/gitlab-ci.json?private_token=Wvjy2Krpb7y8xi93owUz")
+      .with(headers: { 'Content-Type' => 'application/json' })
+      .to_return(status: 200, body: "", headers: {})
   end
 
   def project_hash_array
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 3f472e59c4973c493b7032206676eb97f813a51c..32546abcad4aabb45a221dce6b3a2815a112573c 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -83,13 +83,13 @@ module TestEnv
   end
 
   def disable_mailer
-    allow_any_instance_of(NotificationService).to receive(:mailer).
-      and_return(double.as_null_object)
+    allow_any_instance_of(NotificationService).to receive(:mailer)
+      .and_return(double.as_null_object)
   end
 
   def enable_mailer
-    allow_any_instance_of(NotificationService).to receive(:mailer).
-      and_call_original
+    allow_any_instance_of(NotificationService).to receive(:mailer)
+      .and_call_original
   end
 
   def disable_pre_receive
@@ -120,18 +120,21 @@ module TestEnv
   end
 
   def setup_gitlab_shell
-    unless File.directory?(Gitlab.config.gitlab_shell.path)
-      unless system('rake', 'gitlab:shell:install')
-        raise 'Can`t clone gitlab-shell'
-      end
+    shell_needs_update = component_needs_update?(Gitlab.config.gitlab_shell.path,
+      Gitlab::Shell.version_required)
+
+    unless !shell_needs_update || system('rake', 'gitlab:shell:install')
+      raise 'Can`t clone gitlab-shell'
     end
   end
 
   def setup_gitaly
     socket_path = Gitlab::GitalyClient.address('default').sub(/\Aunix:/, '')
     gitaly_dir = File.dirname(socket_path)
+    gitaly_needs_update = component_needs_update?(gitaly_dir,
+      Gitlab::GitalyClient.expected_server_version)
 
-    unless !gitaly_needs_update?(gitaly_dir) || system('rake', "gitlab:gitaly:install[#{gitaly_dir}]")
+    unless !gitaly_needs_update || system('rake', "gitlab:gitaly:install[#{gitaly_dir}]")
       raise "Can't clone gitaly"
     end
 
@@ -261,13 +264,13 @@ module TestEnv
     end
   end
 
-  def gitaly_needs_update?(gitaly_dir)
-    gitaly_version = File.read(File.join(gitaly_dir, 'VERSION')).strip
+  def component_needs_update?(component_folder, expected_version)
+    version = File.read(File.join(component_folder, 'VERSION')).strip
 
     # Notice that this will always yield true when using branch versions
     # (`=branch_name`), but that actually makes sure the server is always based
     # on the latest branch revision.
-    gitaly_version != Gitlab::GitalyClient.expected_server_version
+    version != expected_version
   rescue Errno::ENOENT
     true
   end
diff --git a/spec/support/time_tracking_shared_examples.rb b/spec/support/time_tracking_shared_examples.rb
index b407b8097d27e3e83de17f6464164c81c47bc276..0fa74f911f65d940c651197a42fdf323b5145ea4 100644
--- a/spec/support/time_tracking_shared_examples.rb
+++ b/spec/support/time_tracking_shared_examples.rb
@@ -54,7 +54,7 @@ shared_examples 'issuable time tracker' do
   it 'shows the help state when icon is clicked' do
     page.within '.time-tracking-component-wrap' do
       find('.help-button').click
-      expect(page).to have_content 'Track time with slash commands'
+      expect(page).to have_content 'Track time with quick actions'
       expect(page).to have_content 'Learn more'
     end
   end
@@ -64,7 +64,7 @@ shared_examples 'issuable time tracker' do
       find('.help-button').click
       find('.close-help-button').click
 
-      expect(page).not_to have_content 'Track time with slash commands'
+      expect(page).not_to have_content 'Track time with quick actions'
       expect(page).not_to have_content 'Learn more'
     end
   end
@@ -78,8 +78,8 @@ shared_examples 'issuable time tracker' do
   end
 end
 
-def submit_time(slash_command)
-  fill_in 'note[note]', with: slash_command
+def submit_time(quick_action)
+  fill_in 'note[note]', with: quick_action
   find('.js-comment-submit-button').trigger('click')
   wait_for_requests
 end
diff --git a/spec/support/unpack-gitlab-git-test b/spec/support/unpack-gitlab-git-test
new file mode 100755
index 0000000000000000000000000000000000000000..d5b4912457da90fa137ffa3d75a1fb8081d9a177
--- /dev/null
+++ b/spec/support/unpack-gitlab-git-test
@@ -0,0 +1,38 @@
+#!/usr/bin/env ruby
+require 'fileutils'
+
+REPO = 'spec/support/gitlab-git-test.git'.freeze
+PACK_DIR = REPO + '/objects/pack'
+GIT = %W[git --git-dir=#{REPO}].freeze
+BASE_PACK = 'pack-691247af2a6acb0b63b73ac0cb90540e93614043'.freeze
+
+def main
+  unpack
+  # We want to store the refs in a packed-refs file because if we don't
+  # they can get mangled by filesystems.
+  abort unless system(*GIT, *%w[pack-refs --all])
+  abort unless system(*GIT, 'fsck')
+end
+
+# We don't want contributors to commit new pack files because those
+# create unnecessary churn.
+def unpack
+  pack_files = Dir[File.join(PACK_DIR, '*')].reject do |pack|
+    pack.start_with?(File.join(PACK_DIR, BASE_PACK))
+  end
+  return if pack_files.empty?
+
+  pack_files.each do |pack|
+    unless pack.end_with?('.pack')
+      FileUtils.rm(pack)
+      next
+    end
+
+    File.open(pack, 'rb') do |open_pack|
+      File.unlink(pack)
+      abort unless system(*GIT, 'unpack-objects', in: open_pack)
+    end
+  end
+end
+
+main
diff --git a/spec/support/update_invalid_issuable.rb b/spec/support/update_invalid_issuable.rb
index 365c34448ac700be0c627661f32077ba479951ea..1490287681bccf2bf8dd7fa8c6766003648fe35b 100644
--- a/spec/support/update_invalid_issuable.rb
+++ b/spec/support/update_invalid_issuable.rb
@@ -21,8 +21,8 @@ shared_examples 'update invalid issuable' do |klass|
 
   context 'when updating causes conflicts' do
     before do
-      allow_any_instance_of(issuable.class).to receive(:save).
-        and_raise(ActiveRecord::StaleObjectError.new(issuable, :save))
+      allow_any_instance_of(issuable.class).to receive(:save)
+        .and_raise(ActiveRecord::StaleObjectError.new(issuable, :save))
     end
 
     it 'renders edit when format is html' do
diff --git a/spec/support/user_activities_helpers.rb b/spec/support/user_activities_helpers.rb
index f7ca9a31edd4e4e2b71d8dd96d04d6997a4ad352..44feb104644e8b15f0cef8b79e5ba22188e5f1d3 100644
--- a/spec/support/user_activities_helpers.rb
+++ b/spec/support/user_activities_helpers.rb
@@ -1,7 +1,7 @@
 module UserActivitiesHelpers
   def user_activity(user)
-    Gitlab::UserActivities.new.
-      find { |k, _| k == user.id.to_s }&.
+    Gitlab::UserActivities.new
+      .find { |k, _| k == user.id.to_s }&.
       second
   end
 end
diff --git a/spec/support/wait_for_requests.rb b/spec/support/wait_for_requests.rb
index 1cbe609c0e029437dc5da2a096d73ab187ca5a27..b5c3c0f55b8dcd784ed046e3545756097ebe72b9 100644
--- a/spec/support/wait_for_requests.rb
+++ b/spec/support/wait_for_requests.rb
@@ -53,9 +53,3 @@ module WaitForRequests
     Capybara.current_driver == Capybara.javascript_driver
   end
 end
-
-RSpec.configure do |config|
-  config.after(:each, :js) do
-    block_and_wait_for_requests_complete
-  end
-end
diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb
index 1e5f55a738a2bccf90b33df0c059c1c9831854d7..71580a788d09ed6ffa9c8ef7b53b17eefd72d94b 100644
--- a/spec/tasks/gitlab/backup_rake_spec.rb
+++ b/spec/tasks/gitlab/backup_rake_spec.rb
@@ -47,24 +47,24 @@ describe 'gitlab:app namespace rake task' do
         allow(Kernel).to receive(:system).and_return(true)
         allow(FileUtils).to receive(:cp_r).and_return(true)
         allow(FileUtils).to receive(:mv).and_return(true)
-        allow(Rake::Task["gitlab:shell:setup"]).
-          to receive(:invoke).and_return(true)
+        allow(Rake::Task["gitlab:shell:setup"])
+          .to receive(:invoke).and_return(true)
         ENV['force'] = 'yes'
       end
 
       let(:gitlab_version) { Gitlab::VERSION }
 
       it 'fails on mismatch' do
-        allow(YAML).to receive(:load_file).
-          and_return({ gitlab_version: "not #{gitlab_version}" })
+        allow(YAML).to receive(:load_file)
+          .and_return({ gitlab_version: "not #{gitlab_version}" })
 
-        expect { run_rake_task('gitlab:backup:restore') }.
-          to raise_error(SystemExit)
+        expect { run_rake_task('gitlab:backup:restore') }
+          .to raise_error(SystemExit)
       end
 
       it 'invokes restoration on match' do
-        allow(YAML).to receive(:load_file).
-          and_return({ gitlab_version: gitlab_version })
+        allow(YAML).to receive(:load_file)
+          .and_return({ gitlab_version: gitlab_version })
         expect(Rake::Task['gitlab:db:drop_tables']).to receive(:invoke)
         expect(Rake::Task['gitlab:backup:db:restore']).to receive(:invoke)
         expect(Rake::Task['gitlab:backup:repo:restore']).to receive(:invoke)
@@ -310,8 +310,8 @@ describe 'gitlab:app namespace rake task' do
     end
 
     it 'does not invoke repositories restore' do
-      allow(Rake::Task['gitlab:shell:setup']).
-        to receive(:invoke).and_return(true)
+      allow(Rake::Task['gitlab:shell:setup'])
+        .to receive(:invoke).and_return(true)
       allow($stdout).to receive :write
 
       expect(Rake::Task['gitlab:db:drop_tables']).to receive :invoke
diff --git a/spec/tasks/gitlab/gitaly_rake_spec.rb b/spec/tasks/gitlab/gitaly_rake_spec.rb
index cfa6c9ca8ce3401c1de36fac79d82096e94845fa..d42d2423f151344fc198cf8d4343c94cba7fde12 100644
--- a/spec/tasks/gitlab/gitaly_rake_spec.rb
+++ b/spec/tasks/gitlab/gitaly_rake_spec.rb
@@ -20,8 +20,8 @@ describe 'gitlab:gitaly namespace rake task' do
 
     context 'when an underlying Git command fail' do
       it 'aborts and display a help message' do
-        expect_any_instance_of(Object).
-          to receive(:checkout_or_clone_version).and_raise 'Git error'
+        expect_any_instance_of(Object)
+          .to receive(:checkout_or_clone_version).and_raise 'Git error'
 
         expect { run_rake_task('gitlab:gitaly:install', clone_path) }.to raise_error 'Git error'
       end
@@ -33,8 +33,8 @@ describe 'gitlab:gitaly namespace rake task' do
       end
 
       it 'calls checkout_or_clone_version with the right arguments' do
-        expect_any_instance_of(Object).
-          to receive(:checkout_or_clone_version).with(version: version, repo: repo, target_dir: clone_path)
+        expect_any_instance_of(Object)
+          .to receive(:checkout_or_clone_version).with(version: version, repo: repo, target_dir: clone_path)
 
         run_rake_task('gitlab:gitaly:install', clone_path)
       end
@@ -89,6 +89,7 @@ describe 'gitlab:gitaly namespace rake task' do
         }
       }
       allow(Gitlab.config.repositories).to receive(:storages).and_return(config)
+      allow(Rails.env).to receive(:test?).and_return(false)
 
       expected_output = ''
       Timecop.freeze do
@@ -105,8 +106,8 @@ describe 'gitlab:gitaly namespace rake task' do
         TOML
       end
 
-      expect { run_rake_task('gitlab:gitaly:storage_config')}.
-        to output(expected_output).to_stdout
+      expect { run_rake_task('gitlab:gitaly:storage_config')}
+        .to output(expected_output).to_stdout
 
       parsed_output = TOML.parse(expected_output)
       config.each do |name, params|
diff --git a/spec/tasks/gitlab/task_helpers_spec.rb b/spec/tasks/gitlab/task_helpers_spec.rb
index 3d9ba7cdc6f4a74e32d3741c4d86c04f54485c83..91cc684d0325670391c6aeedc7bc8c47c1edd132 100644
--- a/spec/tasks/gitlab/task_helpers_spec.rb
+++ b/spec/tasks/gitlab/task_helpers_spec.rb
@@ -60,8 +60,8 @@ describe Gitlab::TaskHelpers do
 
   describe '#clone_repo' do
     it 'clones the repo in the target dir' do
-      expect(subject).
-        to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} clone -- #{repo} #{clone_path}])
+      expect(subject)
+        .to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} clone -- #{repo} #{clone_path}])
 
       subject.clone_repo(repo, clone_path)
     end
@@ -69,10 +69,10 @@ describe Gitlab::TaskHelpers do
 
   describe '#checkout_version' do
     it 'clones the repo in the target dir' do
-      expect(subject).
-        to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} fetch --quiet])
-      expect(subject).
-        to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} checkout --quiet #{tag}])
+      expect(subject)
+        .to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} fetch --quiet])
+      expect(subject)
+        .to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} checkout --quiet #{tag}])
 
       subject.checkout_version(tag, clone_path)
     end
@@ -80,8 +80,8 @@ describe Gitlab::TaskHelpers do
 
   describe '#reset_to_version' do
     it 'resets --hard to the given version' do
-      expect(subject).
-        to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} reset --hard #{tag}])
+      expect(subject)
+        .to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} reset --hard #{tag}])
 
       subject.reset_to_version(tag, clone_path)
     end
diff --git a/spec/tasks/gitlab/workhorse_rake_spec.rb b/spec/tasks/gitlab/workhorse_rake_spec.rb
index 63d1cf2bbe59732bb985a71dfb1386b2eccd0229..1b68f3044a495df648978d4cdb19010effb1f1c0 100644
--- a/spec/tasks/gitlab/workhorse_rake_spec.rb
+++ b/spec/tasks/gitlab/workhorse_rake_spec.rb
@@ -20,8 +20,8 @@ describe 'gitlab:workhorse namespace rake task' do
 
     context 'when an underlying Git command fail' do
       it 'aborts and display a help message' do
-        expect_any_instance_of(Object).
-          to receive(:checkout_or_clone_version).and_raise 'Git error'
+        expect_any_instance_of(Object)
+          .to receive(:checkout_or_clone_version).and_raise 'Git error'
 
         expect { run_rake_task('gitlab:workhorse:install', clone_path) }.to raise_error 'Git error'
       end
@@ -33,8 +33,8 @@ describe 'gitlab:workhorse namespace rake task' do
       end
 
       it 'calls checkout_or_clone_version with the right arguments' do
-        expect_any_instance_of(Object).
-          to receive(:checkout_or_clone_version).with(version: version, repo: repo, target_dir: clone_path)
+        expect_any_instance_of(Object)
+          .to receive(:checkout_or_clone_version).with(version: version, repo: repo, target_dir: clone_path)
 
         run_rake_task('gitlab:workhorse:install', clone_path)
       end
diff --git a/spec/validators/dynamic_path_validator_spec.rb b/spec/validators/dynamic_path_validator_spec.rb
index 8dbf3eecd23aeb1af4c9b57bee9e42a01e46755a..8bd5306ff986f771890129b042d3e3c32277edce 100644
--- a/spec/validators/dynamic_path_validator_spec.rb
+++ b/spec/validators/dynamic_path_validator_spec.rb
@@ -84,5 +84,14 @@ describe DynamicPathValidator do
 
       expect(group.errors[:path]).to include('users is a reserved name')
     end
+
+    it 'updating to an invalid path is not allowed' do
+      project = create(:empty_project)
+      project.path = 'update'
+
+      validator.validate_each(project, :path, 'update')
+
+      expect(project.errors[:path]).to include('update is a reserved name')
+    end
   end
 end
diff --git a/spec/views/ci/status/_badge.html.haml_spec.rb b/spec/views/ci/status/_badge.html.haml_spec.rb
index 72323da283834829563dfcf735495781fa346b9d..6a4738ba44393723c8bf200b4fbcb1e82ed4ae32 100644
--- a/spec/views/ci/status/_badge.html.haml_spec.rb
+++ b/spec/views/ci/status/_badge.html.haml_spec.rb
@@ -16,8 +16,7 @@ describe 'ci/status/_badge', :view do
       end
 
       it 'has link to build details page' do
-        details_path = namespace_project_job_path(
-          project.namespace, project, build)
+        details_path = project_job_path(project, build)
 
         render_status(build)
 
diff --git a/spec/views/devise/shared/_signin_box.html.haml_spec.rb b/spec/views/devise/shared/_signin_box.html.haml_spec.rb
index 1397bfa5864e8991f3b24a7fb8548467cb3435a1..9adbb0476bed319a175b62557ecff1e39b5c26ae 100644
--- a/spec/views/devise/shared/_signin_box.html.haml_spec.rb
+++ b/spec/views/devise/shared/_signin_box.html.haml_spec.rb
@@ -31,7 +31,7 @@ describe 'devise/shared/_signin_box' do
   def enable_crowd
     allow(view).to receive(:form_based_providers).and_return([:crowd])
     allow(view).to receive(:crowd_enabled?).and_return(true)
-    allow(view).to receive(:omniauth_authorize_path).with(:user, :crowd).
-      and_return('/crowd')
+    allow(view).to receive(:omniauth_authorize_path).with(:user, :crowd)
+      .and_return('/crowd')
   end
 end
diff --git a/spec/views/profiles/show.html.haml_spec.rb b/spec/views/profiles/show.html.haml_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e89a8cb962659e23e4f0841e943626835e2ade4a
--- /dev/null
+++ b/spec/views/profiles/show.html.haml_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe 'profiles/show' do
+  let(:user) { create(:user) }
+
+  before do
+    assign(:user, user)
+    allow(controller).to receive(:current_user).and_return(user)
+  end
+
+  context 'when the profile page is opened' do
+    it 'displays the correct elements' do
+      render
+
+      expect(rendered).to have_field('user_name', user.name)
+      expect(rendered).to have_field('user_id', user.id)
+    end
+  end
+end
diff --git a/spec/views/projects/commit/show.html.haml_spec.rb b/spec/views/projects/commit/show.html.haml_spec.rb
index 122075cc10e143c02c262836624d9d6f46a84a81..92b4aa12d49a3d23d0e07e4791b091ca5f874ae8 100644
--- a/spec/views/projects/commit/show.html.haml_spec.rb
+++ b/spec/views/projects/commit/show.html.haml_spec.rb
@@ -21,24 +21,26 @@ describe 'projects/commit/show.html.haml', :view do
   context 'inline diff view' do
     before do
       allow(view).to receive(:diff_view).and_return(:inline)
+      allow(view).to receive(:diff_view).and_return(:inline)
 
       render
     end
 
-    it 'keeps container-limited' do
-      expect(rendered).not_to have_selector('.limit-container-width')
+    it 'has limited width' do
+      expect(rendered).to have_selector('.limit-container-width')
     end
   end
 
   context 'parallel diff view' do
     before do
       allow(view).to receive(:diff_view).and_return(:parallel)
+      allow(view).to receive(:fluid_layout).and_return(true)
 
       render
     end
 
     it 'spans full width' do
-      expect(rendered).to have_selector('.limit-container-width')
+      expect(rendered).not_to have_selector('.limit-container-width')
     end
   end
 end
diff --git a/spec/views/projects/merge_requests/_commits.html.haml_spec.rb b/spec/views/projects/merge_requests/_commits.html.haml_spec.rb
index 4052dbf8df311b60b94eaaff439302ac80925c0d..98c7de9b709fbcf4cbdcc0f9167e7be4b818a487 100644
--- a/spec/views/projects/merge_requests/_commits.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/_commits.html.haml_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe 'projects/merge_requests/show/_commits.html.haml' do
+describe 'projects/merge_requests/_commits.html.haml' do
   include Devise::Test::ControllerHelpers
 
   let(:user) { create(:user) }
@@ -25,10 +25,7 @@ describe 'projects/merge_requests/show/_commits.html.haml' do
     render
 
     commit = source_project.commit(merge_request.source_branch)
-    href = namespace_project_commit_path(
-      source_project.namespace,
-      source_project,
-      commit)
+    href = project_commit_path(source_project, commit)
 
     expect(rendered).to have_link(Commit.truncate_sha(commit.sha), href: href)
   end
diff --git a/spec/views/projects/merge_requests/_new_submit.html.haml_spec.rb b/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb
similarity index 92%
rename from spec/views/projects/merge_requests/_new_submit.html.haml_spec.rb
rename to spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb
index 4f698a34ab53b83dc40c794517d372c203d0f4de..1e9bdf9108fd80b7b19e65e91b86cbd1fc41ef0b 100644
--- a/spec/views/projects/merge_requests/_new_submit.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe 'projects/merge_requests/_new_submit.html.haml', :view do
+describe 'projects/merge_requests/creations/_new_submit.html.haml', :view do
   let(:merge_request) { create(:merge_request) }
   let!(:pipeline) { create(:ci_empty_pipeline) }
 
diff --git a/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb b/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e56c0f6be038deffd775e6d220e2db13827b864a
--- /dev/null
+++ b/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+describe 'projects/notes/_more_actions_dropdown', :view do
+  let(:author_user) { create(:user) }
+  let(:not_author_user) { create(:user) }
+
+  let(:project) { create(:empty_project) }
+  let(:issue) { create(:issue, project: project) }
+  let!(:note) { create(:note_on_issue, author: author_user, noteable: issue, project: project) }
+
+  before do
+    assign(:project, project)
+  end
+
+  it 'shows Report as abuse button if not editable and not current users comment' do
+    render 'projects/notes/more_actions_dropdown', current_user: not_author_user, note_editable: false, note: note
+
+    expect(rendered).to have_link('Report as abuse')
+  end
+
+  it 'does not show the More actions button if not editable and current users comment' do
+    render 'projects/notes/more_actions_dropdown', current_user: author_user, note_editable: false, note: note
+
+    expect(rendered).not_to have_selector('.dropdown.more-actions')
+  end
+
+  it 'shows Report as abuse, Edit and Delete buttons if editable and not current users comment' do
+    render 'projects/notes/more_actions_dropdown', current_user: not_author_user, note_editable: true, note: note
+
+    expect(rendered).to have_link('Report as abuse')
+    expect(rendered).to have_button('Edit comment')
+    expect(rendered).to have_link('Delete comment')
+  end
+
+  it 'shows Edit and Delete buttons if editable and current users comment' do
+    render 'projects/notes/more_actions_dropdown', current_user: author_user, note_editable: true, note: note
+
+    expect(rendered).to have_button('Edit comment')
+    expect(rendered).to have_link('Delete comment')
+  end
+end
diff --git a/spec/views/shared/notes/_form.html.haml_spec.rb b/spec/views/shared/notes/_form.html.haml_spec.rb
index d7d0a5bf56a9b992e78ecff60b276e12842b9a4f..cae6bee2776d7cfe715a2196e800c928d819ecfc 100644
--- a/spec/views/shared/notes/_form.html.haml_spec.rb
+++ b/spec/views/shared/notes/_form.html.haml_spec.rb
@@ -20,8 +20,8 @@ describe 'shared/notes/_form' do
     context "with a note on #{noteable}" do
       let(:note) { build(:"note_on_#{noteable}", project: project) }
 
-      it 'says that markdown and slash commands are supported' do
-        expect(rendered).to have_content('Markdown and slash commands are supported')
+      it 'says that markdown and quick actions are supported' do
+        expect(rendered).to have_content('Markdown and quick actions are supported')
       end
     end
   end
@@ -29,7 +29,7 @@ describe 'shared/notes/_form' do
   context 'with a note on a commit' do
     let(:note) { build(:note_on_commit, project: project) }
 
-    it 'says that only markdown is supported, not slash commands' do
+    it 'says that only markdown is supported, not quick actions' do
       expect(rendered).to have_content('Markdown is supported')
     end
   end
diff --git a/spec/workers/background_migration_worker_spec.rb b/spec/workers/background_migration_worker_spec.rb
index 0d742ae9dc7e35942caf756a7f1f107eb460b169..85939429feb80623611e7ea9420fdd27042e60fe 100644
--- a/spec/workers/background_migration_worker_spec.rb
+++ b/spec/workers/background_migration_worker_spec.rb
@@ -3,9 +3,9 @@ require 'spec_helper'
 describe BackgroundMigrationWorker do
   describe '.perform' do
     it 'performs a background migration' do
-      expect(Gitlab::BackgroundMigration).
-        to receive(:perform).
-        with('Foo', [10, 20])
+      expect(Gitlab::BackgroundMigration)
+        .to receive(:perform)
+        .with('Foo', [10, 20])
 
       described_class.new.perform('Foo', [10, 20])
     end
diff --git a/spec/workers/delete_user_worker_spec.rb b/spec/workers/delete_user_worker_spec.rb
index 5912dd76262fbd9d954763d3068cd5c182214fc4..3659451500504cc0d6f10311c52a4d46bfabbc69 100644
--- a/spec/workers/delete_user_worker_spec.rb
+++ b/spec/workers/delete_user_worker_spec.rb
@@ -5,15 +5,15 @@ describe DeleteUserWorker do
   let!(:current_user) { create(:user) }
 
   it "calls the DeleteUserWorker with the params it was given" do
-    expect_any_instance_of(Users::DestroyService).to receive(:execute).
-                                                      with(user, {})
+    expect_any_instance_of(Users::DestroyService).to receive(:execute)
+                                                      .with(user, {})
 
     described_class.new.perform(current_user.id, user.id)
   end
 
   it "uses symbolized keys" do
-    expect_any_instance_of(Users::DestroyService).to receive(:execute).
-                                                      with(user, test: "test")
+    expect_any_instance_of(Users::DestroyService).to receive(:execute)
+                                                      .with(user, test: "test")
 
     described_class.new.perform(current_user.id, user.id, "test" => "test")
   end
diff --git a/spec/workers/every_sidekiq_worker_spec.rb b/spec/workers/every_sidekiq_worker_spec.rb
index fc9adf47c1e833ee529bea87e3666dff8d16a9ac..30908534eb3b6e1399086ec88b07d6bb9d5c5e37 100644
--- a/spec/workers/every_sidekiq_worker_spec.rb
+++ b/spec/workers/every_sidekiq_worker_spec.rb
@@ -5,8 +5,8 @@ describe 'Every Sidekiq worker' do
     root = Rails.root.join('app', 'workers')
     concerns = root.join('concerns').to_s
 
-    workers = Dir[root.join('**', '*.rb')].
-      reject { |path| path.start_with?(concerns) }
+    workers = Dir[root.join('**', '*.rb')]
+      .reject { |path| path.start_with?(concerns) }
 
     workers.map do |path|
       ns = Pathname.new(path).relative_path_from(root).to_s.gsub('.rb', '')
@@ -22,9 +22,9 @@ describe 'Every Sidekiq worker' do
   end
 
   it 'uses the cronjob queue when the worker runs as a cronjob' do
-    cron_workers = Settings.cron_jobs.
-      map { |job_name, options| options['job_class'].constantize }.
-      to_set
+    cron_workers = Settings.cron_jobs
+      .map { |job_name, options| options['job_class'].constantize }
+      .to_set
 
     workers.each do |worker|
       next unless cron_workers.include?(worker)
diff --git a/spec/workers/expire_pipeline_cache_worker_spec.rb b/spec/workers/expire_pipeline_cache_worker_spec.rb
index 28e5b706803215ced0fa5d8ffa996e1443f9c9f9..e4f78999489e4a71b6fe8bef5f37ddb7e77c1b65 100644
--- a/spec/workers/expire_pipeline_cache_worker_spec.rb
+++ b/spec/workers/expire_pipeline_cache_worker_spec.rb
@@ -37,8 +37,8 @@ describe ExpirePipelineCacheWorker do
     end
 
     it 'updates the cached status for a project' do
-      expect(Gitlab::Cache::Ci::ProjectPipelineStatus).to receive(:update_for_pipeline).
-                                                            with(pipeline)
+      expect(Gitlab::Cache::Ci::ProjectPipelineStatus).to receive(:update_for_pipeline)
+                                                            .with(pipeline)
 
       subject.perform(pipeline.id)
     end
diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb
index f443bb2c9b46914a41c54f0e163e9e609e9d4814..309b3172da11fda574f5df301e9a7dbaa45c5310 100644
--- a/spec/workers/git_garbage_collect_worker_spec.rb
+++ b/spec/workers/git_garbage_collect_worker_spec.rb
@@ -11,8 +11,8 @@ describe GitGarbageCollectWorker do
   describe "#perform" do
     it "flushes ref caches when the task is 'gc'" do
       expect(subject).to receive(:command).with(:gc).and_return([:the, :command])
-      expect(Gitlab::Popen).to receive(:popen).
-        with([:the, :command], project.repository.path_to_repo).and_return(["", 0])
+      expect(Gitlab::Popen).to receive(:popen)
+        .with([:the, :command], project.repository.path_to_repo).and_return(["", 0])
 
       expect_any_instance_of(Repository).to receive(:after_create_branch).and_call_original
       expect_any_instance_of(Repository).to receive(:branch_names).and_call_original
diff --git a/spec/workers/new_note_worker_spec.rb b/spec/workers/new_note_worker_spec.rb
index 8fdbb35afd0d640b64283b0638c2d1cc7db85f29..575361c93d4ac99641926b2543d036b7189ccf99 100644
--- a/spec/workers/new_note_worker_spec.rb
+++ b/spec/workers/new_note_worker_spec.rb
@@ -24,8 +24,8 @@ describe NewNoteWorker do
     let(:unexistent_note_id) { 999 }
 
     it 'logs NewNoteWorker process skipping' do
-      expect(Rails.logger).to receive(:error).
-        with("NewNoteWorker: couldn't find note with ID=999, skipping job")
+      expect(Rails.logger).to receive(:error)
+        .with("NewNoteWorker: couldn't find note with ID=999, skipping job")
 
       described_class.new.perform(unexistent_note_id)
     end
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index 3c93da63f2e04eadb17944531fc100b3cc465cfe..a8f4bb72acf50d4a779654a729bb89fdb7ed0140 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -4,7 +4,7 @@ describe PostReceive do
   let(:changes) { "123456 789012 refs/heads/tést\n654321 210987 refs/tags/tag" }
   let(:wrongly_encoded_changes) { changes.encode("ISO-8859-1").force_encoding("UTF-8") }
   let(:base64_changes) { Base64.encode64(wrongly_encoded_changes) }
-  let(:project_identifier) { "project-#{project.id}" }
+  let(:gl_repository) { "project-#{project.id}" }
   let(:key) { create(:key, user: project.owner) }
   let(:key_id) { key.shell_id }
 
@@ -19,22 +19,14 @@ describe PostReceive do
   end
 
   context 'with a non-existing project' do
-    let(:project_identifier) { "project-123456789" }
+    let(:gl_repository) { "project-123456789" }
     let(:error_message) do
-      "Triggered hook for non-existing project with identifier \"#{project_identifier}\""
+      "Triggered hook for non-existing project with gl_repository \"#{gl_repository}\""
     end
 
     it "returns false and logs an error" do
       expect(Gitlab::GitLogger).to receive(:error).with("POST-RECEIVE: #{error_message}")
-      expect(described_class.new.perform(project_identifier, key_id, base64_changes)).to be(false)
-    end
-  end
-
-  context "with an absolute path as the project identifier" do
-    it "searches the project by full path" do
-      expect(Project).to receive(:find_by_full_path).with(project.full_path).and_call_original
-
-      described_class.new.perform(pwd(project), key_id, base64_changes)
+      expect(described_class.new.perform(gl_repository, key_id, base64_changes)).to be(false)
     end
   end
 
@@ -49,7 +41,7 @@ describe PostReceive do
       it "calls GitTagPushService" do
         expect_any_instance_of(GitPushService).to receive(:execute).and_return(true)
         expect_any_instance_of(GitTagPushService).not_to receive(:execute)
-        described_class.new.perform(project_identifier, key_id, base64_changes)
+        described_class.new.perform(gl_repository, key_id, base64_changes)
       end
     end
 
@@ -59,7 +51,7 @@ describe PostReceive do
       it "calls GitTagPushService" do
         expect_any_instance_of(GitPushService).not_to receive(:execute)
         expect_any_instance_of(GitTagPushService).to receive(:execute).and_return(true)
-        described_class.new.perform(project_identifier, key_id, base64_changes)
+        described_class.new.perform(gl_repository, key_id, base64_changes)
       end
     end
 
@@ -69,12 +61,12 @@ describe PostReceive do
       it "does not call any of the services" do
         expect_any_instance_of(GitPushService).not_to receive(:execute)
         expect_any_instance_of(GitTagPushService).not_to receive(:execute)
-        described_class.new.perform(project_identifier, key_id, base64_changes)
+        described_class.new.perform(gl_repository, key_id, base64_changes)
       end
     end
 
     context "gitlab-ci.yml" do
-      subject { described_class.new.perform(project_identifier, key_id, base64_changes) }
+      subject { described_class.new.perform(gl_repository, key_id, base64_changes) }
 
       context "creates a Ci::Pipeline for every change" do
         before do
@@ -111,7 +103,7 @@ describe PostReceive do
       it 'calls SystemHooksService' do
         expect_any_instance_of(SystemHooksService).to receive(:execute_hooks).with(fake_hook_data, :repository_update_hooks).and_return(true)
 
-        described_class.new.perform(project_identifier, key_id, base64_changes)
+        described_class.new.perform(gl_repository, key_id, base64_changes)
       end
     end
   end
@@ -119,17 +111,17 @@ describe PostReceive do
   context "webhook" do
     it "fetches the correct project" do
       expect(Project).to receive(:find_by).with(id: project.id.to_s)
-      described_class.new.perform(project_identifier, key_id, base64_changes)
+      described_class.new.perform(gl_repository, key_id, base64_changes)
     end
 
     it "does not run if the author is not in the project" do
-      allow_any_instance_of(Gitlab::GitPostReceive).
-        to receive(:identify_using_ssh_key).
-        and_return(nil)
+      allow_any_instance_of(Gitlab::GitPostReceive)
+        .to receive(:identify_using_ssh_key)
+        .and_return(nil)
 
       expect(project).not_to receive(:execute_hooks)
 
-      expect(described_class.new.perform(project_identifier, key_id, base64_changes)).to be_falsey
+      expect(described_class.new.perform(gl_repository, key_id, base64_changes)).to be_falsey
     end
 
     it "asks the project to trigger all hooks" do
@@ -137,18 +129,14 @@ describe PostReceive do
       expect(project).to receive(:execute_hooks).twice
       expect(project).to receive(:execute_services).twice
 
-      described_class.new.perform(project_identifier, key_id, base64_changes)
+      described_class.new.perform(gl_repository, key_id, base64_changes)
     end
 
     it "enqueues a UpdateMergeRequestsWorker job" do
       allow(Project).to receive(:find_by).and_return(project)
       expect(UpdateMergeRequestsWorker).to receive(:perform_async).with(project.id, project.owner.id, any_args)
 
-      described_class.new.perform(project_identifier, key_id, base64_changes)
+      described_class.new.perform(gl_repository, key_id, base64_changes)
     end
   end
-
-  def pwd(project)
-    File.join(Gitlab.config.repositories.storages.default['path'], project.path_with_namespace)
-  end
 end
diff --git a/spec/workers/process_commit_worker_spec.rb b/spec/workers/process_commit_worker_spec.rb
index 4e036285e8c1afb60d9c2f57a6dd32e850dd238a..6ebc94bb5444d402c52cf2b51332109b79b86837 100644
--- a/spec/workers/process_commit_worker_spec.rb
+++ b/spec/workers/process_commit_worker_spec.rb
@@ -48,11 +48,11 @@ describe ProcessCommitWorker do
   describe '#process_commit_message' do
     context 'when pushing to the default branch' do
       it 'closes issues that should be closed per the commit message' do
-        allow(commit).to receive(:safe_message).
-          and_return("Closes #{issue.to_reference}")
+        allow(commit).to receive(:safe_message)
+          .and_return("Closes #{issue.to_reference}")
 
-        expect(worker).to receive(:close_issues).
-          with(project, user, user, commit, [issue])
+        expect(worker).to receive(:close_issues)
+          .with(project, user, user, commit, [issue])
 
         worker.process_commit_message(project, commit, user, user, true)
       end
@@ -60,8 +60,8 @@ describe ProcessCommitWorker do
 
     context 'when pushing to a non-default branch' do
       it 'does not close any issues' do
-        allow(commit).to receive(:safe_message).
-          and_return("Closes #{issue.to_reference}")
+        allow(commit).to receive(:safe_message)
+          .and_return("Closes #{issue.to_reference}")
 
         expect(worker).not_to receive(:close_issues)
 
@@ -102,8 +102,8 @@ describe ProcessCommitWorker do
 
   describe '#update_issue_metrics' do
     it 'updates any existing issue metrics' do
-      allow(commit).to receive(:safe_message).
-        and_return("Closes #{issue.to_reference}")
+      allow(commit).to receive(:safe_message)
+        .and_return("Closes #{issue.to_reference}")
 
       worker.update_issue_metrics(commit, user)
 
@@ -113,8 +113,8 @@ describe ProcessCommitWorker do
     end
 
     it "doesn't execute any queries with false conditions" do
-      allow(commit).to receive(:safe_message).
-        and_return("Lorem Ipsum")
+      allow(commit).to receive(:safe_message)
+        .and_return("Lorem Ipsum")
 
       expect { worker.update_issue_metrics(commit, user) }.not_to make_queries_matching(/WHERE (?:1=0|0=1)/)
     end
@@ -128,8 +128,8 @@ describe ProcessCommitWorker do
     end
 
     it 'parses date strings into Time instances' do
-      commit = worker.
-        build_commit(project, id: '123', authored_date: Time.now.to_s)
+      commit = worker
+        .build_commit(project, id: '123', authored_date: Time.now.to_s)
 
       expect(commit.authored_date).to be_an_instance_of(Time)
     end
diff --git a/spec/workers/project_cache_worker_spec.rb b/spec/workers/project_cache_worker_spec.rb
index a4ba5f7c9430f164de827202aaa92a56a2982a0b..6b1f2ff3227f93db1cc62b4677bf75601da4a9bb 100644
--- a/spec/workers/project_cache_worker_spec.rb
+++ b/spec/workers/project_cache_worker_spec.rb
@@ -7,8 +7,8 @@ describe ProjectCacheWorker do
 
   describe '#perform' do
     before do
-      allow_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain).
-        and_return(true)
+      allow_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain)
+        .and_return(true)
     end
 
     context 'with a non-existing project' do
@@ -39,9 +39,9 @@ describe ProjectCacheWorker do
       end
 
       it 'refreshes the method caches' do
-        expect_any_instance_of(Repository).to receive(:refresh_method_caches).
-          with(%i(readme)).
-          and_call_original
+        expect_any_instance_of(Repository).to receive(:refresh_method_caches)
+          .with(%i(readme))
+          .and_call_original
 
         worker.perform(project.id, %w(readme))
       end
@@ -51,9 +51,9 @@ describe ProjectCacheWorker do
           allow(MarkupHelper).to receive(:gitlab_markdown?).and_return(false)
           allow(MarkupHelper).to receive(:plain?).and_return(true)
 
-          expect_any_instance_of(Repository).to receive(:refresh_method_caches).
-                                                  with(%i(readme)).
-                                                  and_call_original
+          expect_any_instance_of(Repository).to receive(:refresh_method_caches)
+                                                  .with(%i(readme))
+                                                  .and_call_original
           worker.perform(project.id, %w(readme))
         end
       end
@@ -63,9 +63,9 @@ describe ProjectCacheWorker do
   describe '#update_statistics' do
     context 'when a lease could not be obtained' do
       it 'does not update the repository size' do
-        allow(worker).to receive(:try_obtain_lease_for).
-          with(project.id, :update_statistics).
-          and_return(false)
+        allow(worker).to receive(:try_obtain_lease_for)
+          .with(project.id, :update_statistics)
+          .and_return(false)
 
         expect(statistics).not_to receive(:refresh!)
 
@@ -75,9 +75,9 @@ describe ProjectCacheWorker do
 
     context 'when a lease could be obtained' do
       it 'updates the project statistics' do
-        allow(worker).to receive(:try_obtain_lease_for).
-          with(project.id, :update_statistics).
-          and_return(true)
+        allow(worker).to receive(:try_obtain_lease_for)
+          .with(project.id, :update_statistics)
+          .and_return(true)
 
         expect(statistics).to receive(:refresh!)
           .with(only: %i(repository_size))
diff --git a/spec/workers/propagate_service_template_worker_spec.rb b/spec/workers/propagate_service_template_worker_spec.rb
index 7040d5ef81c9780f98f833d4bf098bdee97cfad0..b8b65ead9b36e4ab6871a97efb07ea9b84d601c8 100644
--- a/spec/workers/propagate_service_template_worker_spec.rb
+++ b/spec/workers/propagate_service_template_worker_spec.rb
@@ -15,8 +15,8 @@ describe PropagateServiceTemplateWorker do
   end
 
   before do
-    allow_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain).
-      and_return(true)
+    allow_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain)
+      .and_return(true)
   end
 
   describe '#perform' do
diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb
index 6ea5569b4385f41f9528f71887ef59115b16e446..d9e9409840f6deb23ec293ef4c039562ee1cf05a 100644
--- a/spec/workers/repository_fork_worker_spec.rb
+++ b/spec/workers/repository_fork_worker_spec.rb
@@ -35,11 +35,11 @@ describe RepositoryForkWorker do
         fork_project.namespace.full_path
       ).and_return(true)
 
-      expect_any_instance_of(Repository).to receive(:expire_emptiness_caches).
-        and_call_original
+      expect_any_instance_of(Repository).to receive(:expire_emptiness_caches)
+        .and_call_original
 
-      expect_any_instance_of(Repository).to receive(:expire_exists_cache).
-        and_call_original
+      expect_any_instance_of(Repository).to receive(:expire_exists_cache)
+        .and_call_original
 
       subject.perform(project.id, '/test/path', project.full_path,
                       fork_project.namespace.full_path)
diff --git a/spec/workers/repository_import_worker_spec.rb b/spec/workers/repository_import_worker_spec.rb
index 9c277c501f18800bf9ec3b27f87347df65bc7adf..6b30dabc80e0a33ff0b3868f1b95d4d656ef7f91 100644
--- a/spec/workers/repository_import_worker_spec.rb
+++ b/spec/workers/repository_import_worker_spec.rb
@@ -8,8 +8,8 @@ describe RepositoryImportWorker do
   describe '#perform' do
     context 'when the import was successful' do
       it 'imports a project' do
-        expect_any_instance_of(Projects::ImportService).to receive(:execute).
-          and_return({ status: :ok })
+        expect_any_instance_of(Projects::ImportService).to receive(:execute)
+          .and_return({ status: :ok })
 
         expect_any_instance_of(Repository).to receive(:expire_emptiness_caches)
         expect_any_instance_of(Project).to receive(:import_finish)
diff --git a/vendor/Dockerfile/Binary-alpine.Dockerfile b/vendor/Dockerfile/Binary-alpine.Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..5a9eb2b47163ea8dab97ccee5c2bccbfd3a147e3
--- /dev/null
+++ b/vendor/Dockerfile/Binary-alpine.Dockerfile
@@ -0,0 +1,14 @@
+# This Dockerfile installs a compiled binary into a bare system.
+# You must either commit your compiled binary into source control (not recommended)
+# or build the binary first as part of a CI/CD pipeline.
+
+FROM alpine:3.5
+
+# We'll likely need to add SSL root certificates
+RUN apk --no-cache add ca-certificates
+
+WORKDIR /usr/local/bin
+
+# Change `app` to whatever your binary is called
+Add app .
+CMD ["./app"]
diff --git a/vendor/Dockerfile/Binary-scratch.Dockerfile b/vendor/Dockerfile/Binary-scratch.Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..5e2de2ead61cb4d2e2510a8c6c4a62f726aba545
--- /dev/null
+++ b/vendor/Dockerfile/Binary-scratch.Dockerfile
@@ -0,0 +1,17 @@
+# This Dockerfile installs a compiled binary into an image with no system at all.
+# You must either commit your compiled binary into source control (not recommended)
+# or build the binary first as part of a CI/CD pipeline.
+# Your binary must be statically compiled with no dynamic dependencies on system libraries.
+# e.g. for Docker:
+# CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
+
+FROM scratch
+
+# Since we started from scratch, we'll likely need to add SSL root certificates
+ADD /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
+
+WORKDIR /usr/local/bin
+
+# Change `app` to whatever your binary is called
+Add app .
+CMD ["./app"]
diff --git a/vendor/Dockerfile/Binary.Dockerfile b/vendor/Dockerfile/Binary.Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..e7d560da9accfc75411d166dd47228ce6cf8eb49
--- /dev/null
+++ b/vendor/Dockerfile/Binary.Dockerfile
@@ -0,0 +1,11 @@
+# This Dockerfile installs a compiled binary into a bare system.
+# You must either commit your compiled binary into source control (not recommended)
+# or build the binary first as part of a CI/CD pipeline.
+
+FROM buildpack-deps:jessie
+
+WORKDIR /usr/local/bin
+
+# Change `app` to whatever your binary is called
+Add app .
+CMD ["./app"]
diff --git a/vendor/Dockerfile/Golang-alpine.Dockerfile b/vendor/Dockerfile/Golang-alpine.Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..0287315219b526cda610915b29c6238b040e859c
--- /dev/null
+++ b/vendor/Dockerfile/Golang-alpine.Dockerfile
@@ -0,0 +1,17 @@
+FROM golang:1.8-alpine AS builder
+
+WORKDIR /usr/src/app
+
+COPY . .
+RUN go-wrapper download
+RUN go build -v
+
+FROM alpine:3.5
+
+# We'll likely need to add SSL root certificates
+RUN apk --no-cache add ca-certificates
+
+WORKDIR /usr/local/bin
+
+COPY --from=builder /usr/src/app/app .
+CMD ["./app"]
diff --git a/vendor/Dockerfile/Golang-scratch.Dockerfile b/vendor/Dockerfile/Golang-scratch.Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..9057a2d0e5190e022f5975374d038911b6b5394d
--- /dev/null
+++ b/vendor/Dockerfile/Golang-scratch.Dockerfile
@@ -0,0 +1,20 @@
+FROM golang:1.8-alpine AS builder
+
+# We'll likely need to add SSL root certificates
+RUN apk --no-cache add ca-certificates
+
+WORKDIR /usr/src/app
+
+COPY . .
+RUN go-wrapper download
+RUN CGO_ENABLED=0 GOOS=linux go build -v -a -installsuffix cgo -o app .
+
+FROM scratch
+
+# Since we started from scratch, we'll copy the SSL root certificates from the builder
+COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
+
+WORKDIR /usr/local/bin
+
+COPY --from=builder /usr/src/app/app .
+CMD ["./app"]
diff --git a/vendor/Dockerfile/Golang.Dockerfile b/vendor/Dockerfile/Golang.Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..ec94914be196858b353765fe03c1ec996a411f2f
--- /dev/null
+++ b/vendor/Dockerfile/Golang.Dockerfile
@@ -0,0 +1,14 @@
+FROM golang:1.8 AS builder
+
+WORKDIR /usr/src/app
+
+COPY . .
+RUN go-wrapper download
+RUN go build -v
+
+FROM buildpack-deps:jessie
+
+WORKDIR /usr/local/bin
+
+COPY --from=builder /usr/src/app/app .
+CMD ["./app"]
diff --git a/vendor/Dockerfile/Node-alpine.Dockerfile b/vendor/Dockerfile/Node-alpine.Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..9776b1336b57d7e8776a26b56a4e48cb31fbeb81
--- /dev/null
+++ b/vendor/Dockerfile/Node-alpine.Dockerfile
@@ -0,0 +1,14 @@
+FROM node:7.9-alpine
+
+WORKDIR /usr/src/app
+
+ARG NODE_ENV
+ENV NODE_ENV $NODE_ENV
+COPY package.json /usr/src/app/
+RUN npm install && npm cache clean
+COPY . /usr/src/app
+
+CMD [ "npm", "start" ]
+
+# replace this with your application's default port
+EXPOSE 8888
diff --git a/vendor/Dockerfile/Node.Dockerfile b/vendor/Dockerfile/Node.Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..7e936d5e8870e28062a6834fba3045cbdb48560b
--- /dev/null
+++ b/vendor/Dockerfile/Node.Dockerfile
@@ -0,0 +1,14 @@
+FROM node:7.9
+
+WORKDIR /usr/src/app
+
+ARG NODE_ENV
+ENV NODE_ENV $NODE_ENV
+COPY package.json /usr/src/app/
+RUN npm install && npm cache clean
+COPY . /usr/src/app
+
+CMD [ "npm", "start" ]
+
+# replace this with your application's default port
+EXPOSE 8888
diff --git a/vendor/Dockerfile/Ruby-alpine.Dockerfile b/vendor/Dockerfile/Ruby-alpine.Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..9db4e2130f2a782e4344be731a4377eab1c19221
--- /dev/null
+++ b/vendor/Dockerfile/Ruby-alpine.Dockerfile
@@ -0,0 +1,24 @@
+FROM ruby:2.4-alpine
+
+# Edit with nodejs, mysql-client, postgresql-client, sqlite3, etc. for your needs.
+# Or delete entirely if not needed.
+RUN apk --no-cache add nodejs postgresql-client
+
+# throw errors if Gemfile has been modified since Gemfile.lock
+RUN bundle config --global frozen 1
+
+RUN mkdir -p /usr/src/app
+WORKDIR /usr/src/app
+
+COPY Gemfile Gemfile.lock /usr/src/app/
+RUN bundle install
+
+COPY . /usr/src/app
+
+# For Sinatra
+#EXPOSE 4567
+#CMD ["ruby", "./config.rb"]
+
+# For Rails
+EXPOSE 3000
+CMD ["rails", "server"]
diff --git a/vendor/Dockerfile/Ruby.Dockerfile b/vendor/Dockerfile/Ruby.Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..feb880ee4b24461afd27d4e3c8c4f1007e8dc318
--- /dev/null
+++ b/vendor/Dockerfile/Ruby.Dockerfile
@@ -0,0 +1,27 @@
+FROM ruby:2.4
+
+# Edit with nodejs, mysql-client, postgresql-client, sqlite3, etc. for your needs.
+# Or delete entirely if not needed.
+RUN apt-get update \
+    && apt-get install -y --no-install-recommends \
+        nodejs \
+        postgresql-client \
+    && rm -rf /var/lib/apt/lists/*
+
+# throw errors if Gemfile has been modified since Gemfile.lock
+RUN bundle config --global frozen 1
+
+WORKDIR /usr/src/app
+
+COPY Gemfile Gemfile.lock /usr/src/app/
+RUN bundle install -j $(nproc)
+
+COPY . /usr/src/app
+
+# For Sinatra
+#EXPOSE 4567
+#CMD ["ruby", "./config.rb"]
+
+# For Rails
+EXPOSE 3000
+CMD ["rails", "server", "-b", "0.0.0.0"]
diff --git a/vendor/gitignore/Global/Archives.gitignore b/vendor/gitignore/Global/Archives.gitignore
index f440b808d982be20486fc37febb3ad36375d56e9..43fd5582f915b83653bcf621a8848ab1c32c75f2 100644
--- a/vendor/gitignore/Global/Archives.gitignore
+++ b/vendor/gitignore/Global/Archives.gitignore
@@ -12,11 +12,11 @@
 *.lzma
 *.cab
 
-#packing-only formats
+# Packing-only formats
 *.iso
 *.tar
 
-#package management formats
+# Package management formats
 *.dmg
 *.xpi
 *.gem
diff --git a/vendor/gitignore/Global/JEnv.gitignore b/vendor/gitignore/Global/JEnv.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..d838300ad5ead5c3dd08ebf2e80878315792ca54
--- /dev/null
+++ b/vendor/gitignore/Global/JEnv.gitignore
@@ -0,0 +1,5 @@
+# JEnv local Java version configuration file
+.java-version
+
+# Used by previous versions of JEnv
+.jenv-version
diff --git a/vendor/gitignore/Global/SublimeText.gitignore b/vendor/gitignore/Global/SublimeText.gitignore
index 95ff2244c990f144c262a76386a8ea73616b1a81..86c3fa455aa813552ed648b9c63716e22cb74449 100644
--- a/vendor/gitignore/Global/SublimeText.gitignore
+++ b/vendor/gitignore/Global/SublimeText.gitignore
@@ -1,16 +1,16 @@
-# cache files for sublime text
+# Cache files for Sublime Text
 *.tmlanguage.cache
 *.tmPreferences.cache
 *.stTheme.cache
 
-# workspace files are user-specific
+# Workspace files are user-specific
 *.sublime-workspace
 
-# project files should be checked into the repository, unless a significant
-# proportion of contributors will probably not be using SublimeText
+# Project files should be checked into the repository, unless a significant
+# proportion of contributors will probably not be using Sublime Text
 # *.sublime-project
 
-# sftp configuration file
+# SFTP configuration file
 sftp-config.json
 
 # Package control specific files
diff --git a/vendor/gitignore/Global/Vagrant.gitignore b/vendor/gitignore/Global/Vagrant.gitignore
index a977916f6583710870b00d50dd7fddd6701ece11..93987ca00ecfe04f7d443062255c989329535b3d 100644
--- a/vendor/gitignore/Global/Vagrant.gitignore
+++ b/vendor/gitignore/Global/Vagrant.gitignore
@@ -1 +1,5 @@
+# General
 .vagrant/
+
+# Log files (if you are creating logs in debug mode, uncomment this)
+# *.logs
diff --git a/vendor/gitignore/Global/Vim.gitignore b/vendor/gitignore/Global/Vim.gitignore
index 42e7afc100512b21af0a3339fe851d21e3ee4ed5..6d21783d4715f52acd13c087f94fdf947b26917d 100644
--- a/vendor/gitignore/Global/Vim.gitignore
+++ b/vendor/gitignore/Global/Vim.gitignore
@@ -1,12 +1,14 @@
-# swap
+# Swap
 [._]*.s[a-v][a-z]
 [._]*.sw[a-p]
 [._]s[a-v][a-z]
 [._]sw[a-p]
-# session
+
+# Session
 Session.vim
-# temporary
+
+# Temporary
 .netrwhist
 *~
-# auto-generated tag files
+# Auto-generated tag files
 tags
diff --git a/vendor/gitignore/Global/Windows.gitignore b/vendor/gitignore/Global/Windows.gitignore
index ba26afd9653949ea6f5fdef25505f05bdb7d200f..dff26a9ab70352f72a51a70e4fbf45b0c2e7fbbb 100644
--- a/vendor/gitignore/Global/Windows.gitignore
+++ b/vendor/gitignore/Global/Windows.gitignore
@@ -3,6 +3,9 @@ Thumbs.db
 ehthumbs.db
 ehthumbs_vista.db
 
+# Dump file
+*.stackdump
+
 # Folder config file
 Desktop.ini
 
diff --git a/vendor/gitignore/Global/macOS.gitignore b/vendor/gitignore/Global/macOS.gitignore
index 5972fe50f66e4c7b4b5d87afde97758eeeb7c64f..9d1061e8bc4b0a34cc6c79030549063a372f5e45 100644
--- a/vendor/gitignore/Global/macOS.gitignore
+++ b/vendor/gitignore/Global/macOS.gitignore
@@ -1,3 +1,4 @@
+# General
 *.DS_Store
 .AppleDouble
 .LSOverride
diff --git a/vendor/gitignore/Python.gitignore b/vendor/gitignore/Python.gitignore
index 768d5f400bb1d8395ff692a3a5d9e50f64585a6b..113294a5f18d979085b4ad570d8a22a8e4eabd58 100644
--- a/vendor/gitignore/Python.gitignore
+++ b/vendor/gitignore/Python.gitignore
@@ -8,7 +8,6 @@ __pycache__/
 
 # Distribution / packaging
 .Python
-env/
 build/
 develop-eggs/
 dist/
@@ -43,7 +42,7 @@ htmlcov/
 .cache
 nosetests.xml
 coverage.xml
-*,cover
+*.cover
 .hypothesis/
 
 # Translations
@@ -79,11 +78,10 @@ celerybeat-schedule
 # SageMath parsed files
 *.sage.py
 
-# dotenv
+# Environments
 .env
-
-# virtualenv
 .venv
+env/
 venv/
 ENV/
 
diff --git a/vendor/gitignore/Qt.gitignore b/vendor/gitignore/Qt.gitignore
index 6732e72091c0040f64472470f4fc1ffd08cf6121..5fa47c5a1f21ee324e4ac88ce5f2d0d9017cf114 100644
--- a/vendor/gitignore/Qt.gitignore
+++ b/vendor/gitignore/Qt.gitignore
@@ -12,6 +12,9 @@
 
 # Qt-es
 
+object_script.*.Release
+object_script.*.Debug
+*_plugin_import.cpp
 /.qmake.cache
 /.qmake.stash
 *.pro.user
@@ -26,6 +29,11 @@ ui_*.h
 Makefile*
 *build-*
 
+
+# Qt unit tests
+target_wrapper.*
+
+
 # QtCreator
 
 *.autosave
diff --git a/vendor/gitignore/SugarCRM.gitignore b/vendor/gitignore/SugarCRM.gitignore
index e9270205fd565f86bb2c667f0b82281b24cfaff1..6a183d1c748522dd2a6c4411de27d8fee9c5cac8 100644
--- a/vendor/gitignore/SugarCRM.gitignore
+++ b/vendor/gitignore/SugarCRM.gitignore
@@ -6,7 +6,7 @@
 # the misuse of the repository as backup replacement.
 # For development the cache directory can be safely ignored and
 # therefore it is ignored.
-/cache/
+/cache/*
 !/cache/index.html
 # Ignore some files and directories from the custom directory.
 /custom/history/
@@ -22,6 +22,6 @@
 # Logs files can safely be ignored.
 *.log
 # Ignore the new upload directories.
-/upload/
+/upload/*
 !/upload/index.html
 /upload_backup/
diff --git a/vendor/gitignore/VisualStudio.gitignore b/vendor/gitignore/VisualStudio.gitignore
index 940794e60f21bbcbd6a04b87b08ac92ab9303511..22fd88a55a3a1f5f232f70f6a2eeccdbfd3ea646 100644
--- a/vendor/gitignore/VisualStudio.gitignore
+++ b/vendor/gitignore/VisualStudio.gitignore
@@ -42,6 +42,9 @@ TestResult.xml
 [Rr]eleasePS/
 dlldata.c
 
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
 # .NET Core
 project.lock.json
 project.fragment.lock.json
@@ -183,6 +186,7 @@ AppPackages/
 BundleArtifacts/
 Package.StoreAssociation.xml
 _pkginfo.txt
+*.appx
 
 # Visual Studio cache files
 # files ending in .cache can be ignored
@@ -278,6 +282,9 @@ __pycache__/
 # tools/**
 # !tools/packages.config
 
+# Tabs Studio
+*.tss
+
 # Telerik's JustMock configuration file
 *.jmconfig
 
diff --git a/vendor/gitlab-ci-yml/.gitlab-ci.yml b/vendor/gitlab-ci-yml/.gitlab-ci.yml
index 18b14554887ae0ba16b7d45565a35ebcff4e6051..e2a55163682d1f3ff31d407d5021c78dfc3ecbb8 100644
--- a/vendor/gitlab-ci-yml/.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/.gitlab-ci.yml
@@ -1,4 +1,4 @@
-image: ruby:2.3-alpine
+image: ruby:2.4-alpine
 
 test:
-  script: ruby verify_templates.rb
+  script: ./verify_templates.rb
diff --git a/vendor/gitlab-ci-yml/Crystal.gitlab-ci.yml b/vendor/gitlab-ci-yml/Crystal.gitlab-ci.yml
index 37e44735f7cc03860031c1f202032b5b84d9bea7..02cfab3a5b2edd425efe317531078de081633421 100644
--- a/vendor/gitlab-ci-yml/Crystal.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Crystal.gitlab-ci.yml
@@ -4,7 +4,7 @@ image: "crystallang/crystal:latest"
 
 # Pick zero or more services to be used on all builds.
 # Only needed when using a docker container to run your tests in.
-# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service
+# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-a-service
 # services:
 #   - mysql:latest
 #   - redis:latest
diff --git a/vendor/gitlab-ci-yml/Django.gitlab-ci.yml b/vendor/gitlab-ci-yml/Django.gitlab-ci.yml
index 5ded2f5ce76738b1c55d8a3c8461da028d592afc..57afcbbe8b5eaca0ae462dd28e10ff2ca3390edf 100644
--- a/vendor/gitlab-ci-yml/Django.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Django.gitlab-ci.yml
@@ -4,7 +4,7 @@ image: python:latest
 
 # Pick zero or more services to be used on all builds.
 # Only needed when using a docker container to run your tests in.
-# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service
+# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-a-service
 services:
   - mysql:latest
   - postgres:latest
diff --git a/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml b/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml
index 40648bcd3deb1e000c8443e74a1c5e49f00561bc..5b6af7be8c44b1b4cc99740f372d8118cd82e33a 100644
--- a/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml
@@ -6,8 +6,8 @@ services:
 
 build:
   stage: build
+  before_script:
+    - docker login -u "$CI_REGISTRY_USER" -p "CI_REGISTRY_PASSWORD" $CI_REGISTRY
   script:
-    - export IMAGE_TAG=$(echo -en $CI_COMMIT_REF_NAME | tr -c '[:alnum:]_.-' '-')
-    - docker login -u "gitlab-ci-token" -p "$CI_JOB_TOKEN" $CI_REGISTRY
-    - docker build --pull -t "$CI_REGISTRY_IMAGE:$IMAGE_TAG" .
-    - docker push "$CI_REGISTRY_IMAGE:$IMAGE_TAG"
+    - docker build --pull -t "$CI_REGISTRY_IMAGE:CI_COMMIT_REF_SLUG" .
+    - docker push "$CI_REGISTRY_IMAGE:CI_COMMIT_REF_SLUG"
diff --git a/vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml b/vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml
index 981a77497e20f51a93d2d074649a3418c5ce6d19..cf9c731637c3aa7f10ff977032805c50281d1773 100644
--- a/vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml
@@ -2,7 +2,7 @@ image: elixir:latest
 
 # Pick zero or more services to be used on all builds.
 # Only needed when using a docker container to run your tests in.
-# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service
+# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-a-service
 services:
   - mysql:latest
   - redis:latest
diff --git a/vendor/gitlab-ci-yml/Laravel.gitlab-ci.yml b/vendor/gitlab-ci-yml/Laravel.gitlab-ci.yml
index 0d6a6eddc9714aa2786187524a926b74442b88d4..434de4f055a11633beae5e48701f860900129e20 100644
--- a/vendor/gitlab-ci-yml/Laravel.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Laravel.gitlab-ci.yml
@@ -4,7 +4,7 @@ image: php:latest
 
 # Pick zero or more services to be used on all builds.
 # Only needed when using a docker container to run your tests in.
-# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service
+# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-a-service
 services:
   - mysql:latest
 
diff --git a/vendor/gitlab-ci-yml/Nodejs.gitlab-ci.yml b/vendor/gitlab-ci-yml/Nodejs.gitlab-ci.yml
index e5bce3503f326a7563b9bdb48a55b198280bc9e6..41de145858209ca17aebf3acebb59583bfc74a95 100644
--- a/vendor/gitlab-ci-yml/Nodejs.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Nodejs.gitlab-ci.yml
@@ -4,7 +4,7 @@ image: node:latest
 
 # Pick zero or more services to be used on all builds.
 # Only needed when using a docker container to run your tests in.
-# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service
+# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-a-service
 services:
   - mysql:latest
   - redis:latest
diff --git a/vendor/gitlab-ci-yml/Pages/JBake.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/JBake.gitlab-ci.yml
index bc36a4e6966c5d47955cb7cb2bf03dada3327bac..7abfaf53e8e568b305f9c7394cee8445dbf44464 100644
--- a/vendor/gitlab-ci-yml/Pages/JBake.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Pages/JBake.gitlab-ci.yml
@@ -3,7 +3,7 @@
 #
 # JBake https://jbake.org/ is a Java based, open source, static site/blog generator for developers & designers
 #
-# This yml works with jBake 2.4.0
+# This yml works with jBake 2.5.1
 # Feel free to change JBAKE_VERSION version 
 #
 # HowTo at: https://jorge.aguilera.gitlab.io/howtojbake/
@@ -11,12 +11,12 @@
 image: java:8
 
 variables:
-    JBAKE_VERSION: 2.4.0
+    JBAKE_VERSION: 2.5.1
 
 
 # We use SDKMan as tool for managing versions
 before_script:
-   - apt-get update -qq && apt-get install -y -qq unzip
+   - apt-get update -qq && apt-get install -y -qq unzip zip
    - curl -sSL https://get.sdkman.io | bash
    - echo sdkman_auto_answer=true > /root/.sdkman/etc/config
    - source /root/.sdkman/bin/sdkman-init.sh
diff --git a/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml
index 08b57c8c0acf4f91c914011a6a477afeffe9f180..4e181e854510fd7844595dc4f5b2870c0cbdb422 100644
--- a/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml
@@ -4,7 +4,7 @@ image: "ruby:2.3"
 
 # Pick zero or more services to be used on all builds.
 # Only needed when using a docker container to run your tests in.
-# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service
+# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-a-service
 services:
   - mysql:latest
   - redis:latest
diff --git a/vendor/gitlab-ci-yml/Rust.gitlab-ci.yml b/vendor/gitlab-ci-yml/Rust.gitlab-ci.yml
index ae3f7405ea3335f38ea1d4a234e9eeef4ee7b9f4..7810121c350937977f49b570521430b54548aabc 100644
--- a/vendor/gitlab-ci-yml/Rust.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Rust.gitlab-ci.yml
@@ -4,7 +4,7 @@ image: "scorpil/rust:stable"
 
 # Optional: Pick zero or more services to be used on all builds.
 # Only needed when using a docker container to run your tests in.
-# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service
+# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-a-service
 #services:
 #  - mysql:latest
 #  - redis:latest
diff --git a/vendor/licenses.csv b/vendor/licenses.csv
index a8e7f5e3ea970ccaa14e855301d62edc2481a622..5dcac5947a16a4b9023ebb6cea0fb368bd9ed3d8 100644
--- a/vendor/licenses.csv
+++ b/vendor/licenses.csv
@@ -2,7 +2,7 @@ RedCloth,4.3.2,MIT
 abbrev,1.0.9,ISC
 accepts,1.3.3,MIT
 ace-rails-ap,4.1.2,MIT
-acorn,4.0.11,MIT
+acorn,5.0.3,MIT
 acorn-dynamic-import,2.0.1,MIT
 acorn-jsx,3.0.1,MIT
 actionmailer,4.2.8,MIT
@@ -53,6 +53,7 @@ assert-plus,0.2.0,MIT
 async,0.2.10,MIT
 async-each,1.0.1,MIT
 asynckit,0.4.0,MIT
+atomic,1.1.99,Apache 2.0
 attr_encrypted,3.0.3,MIT
 attr_required,1.0.0,MIT
 autoparse,0.3.3,Apache 2.0
@@ -151,8 +152,9 @@ blob,0.0.4,unknown
 block-stream,0.0.9,ISC
 bluebird,3.4.7,MIT
 bn.js,4.11.6,MIT
-body-parser,1.16.0,MIT
+body-parser,1.17.2,MIT
 boom,2.10.1,New BSD
+bootsnap,1.0.0,MIT
 bootstrap-sass,3.3.6,MIT
 brace-expansion,1.1.6,MIT
 braces,1.8.5,MIT
@@ -222,9 +224,10 @@ compression,1.6.2,MIT
 compression-webpack-plugin,0.3.2,MIT
 concat-map,0.0.1,MIT
 concat-stream,1.6.0,MIT
+concurrent-ruby-ext,1.0.5,MIT
 config-chain,1.1.11,MIT
 configstore,1.4.0,Simplified BSD
-connect,3.5.0,MIT
+connect,3.6.2,MIT
 connect-history-api-fallback,1.3.0,MIT
 connection_pool,2.2.1,MIT
 console-browserify,1.1.0,MIT
@@ -262,8 +265,9 @@ dashdash,1.14.1,MIT
 date-now,0.1.4,MIT
 de-indent,1.0.2,MIT
 debug,2.6.0,MIT
+debugger-ruby_core_source,1.3.8,MIT
 decamelize,1.2.0,MIT
-deckar01-task_list,1.0.6,MIT
+deckar01-task_list,2.0.0,MIT
 deep-extend,0.4.1,MIT
 deep-is,0.1.3,MIT
 default-require-extensions,1.0.0,MIT
@@ -312,8 +316,8 @@ emojis-list,2.1.0,MIT
 encodeurl,1.0.1,MIT
 encryptor,3.0.0,MIT
 end-of-stream,1.0.0,MIT
-engine.io,1.8.2,MIT
-engine.io-client,1.8.2,MIT
+engine.io,1.8.3,MIT
+engine.io-client,1.8.3,MIT
 engine.io-parser,1.3.2,MIT
 enhanced-resolve,3.1.0,MIT
 ent,2.2.0,MIT
@@ -349,7 +353,8 @@ esprima,3.1.3,Simplified BSD
 esrecurse,4.1.0,Simplified BSD
 estraverse,4.1.1,Simplified BSD
 esutils,2.0.2,BSD
-etag,1.7.0,MIT
+et-orbi,1.0.3,MIT
+etag,1.8.0,MIT
 eve-raphael,0.5.0,Apache 2.0
 event-emitter,0.3.4,MIT
 event-stream,3.3.4,MIT
@@ -364,12 +369,11 @@ expand-braces,0.1.2,MIT
 expand-brackets,0.1.5,MIT
 expand-range,1.8.2,MIT
 exports-loader,0.6.4,MIT
-express,4.14.1,MIT
+express,4.15.3,MIT
 expression_parser,0.9.0,MIT
 extend,3.0.0,MIT
 extglob,0.3.2,MIT
 extlib,0.9.16,MIT
-extract-zip,1.5.0,Simplified BSD
 extsprintf,1.0.2,MIT
 faraday,0.11.0,MIT
 faraday_middleware,0.11.0.1,MIT
@@ -378,7 +382,6 @@ fast-levenshtein,2.0.6,MIT
 fast_gettext,1.4.0,"MIT,ruby"
 fastparse,1.1.1,MIT
 faye-websocket,0.7.3,MIT
-fd-slicer,1.0.1,MIT
 ffi,1.9.10,BSD
 figures,1.7.0,MIT
 file-entry-cache,2.0.0,MIT
@@ -387,13 +390,16 @@ filename-regex,2.0.0,MIT
 fileset,2.0.3,MIT
 filesize,3.3.0,New BSD
 fill-range,2.2.3,MIT
-finalhandler,0.5.1,MIT
+finalhandler,1.0.3,MIT
 find-cache-dir,0.1.1,MIT
 find-root,0.1.2,MIT
 find-up,2.1.0,MIT
 flat-cache,1.2.2,MIT
 flatten,1.0.2,MIT
+flipper,0.10.2,MIT
+flipper-active_record,0.10.2,MIT
 flowdock,0.7.1,MIT
+fog-aliyun,0.1.0,MIT
 fog-aws,0.13.0,MIT
 fog-core,1.44.1,MIT
 fog-google,0.5.0,MIT
@@ -409,9 +415,8 @@ forever-agent,0.6.1,Apache 2.0
 form-data,2.1.2,MIT
 formatador,0.2.5,MIT
 forwarded,0.1.0,MIT
-fresh,0.3.0,MIT
+fresh,0.5.0,MIT
 from,0.1.7,MIT
-fs-extra,1.0.0,MIT
 fs.realpath,1.0.0,ISC
 fsevents,,unknown
 fstream,1.0.10,ISC
@@ -427,7 +432,7 @@ get_process_mem,0.2.0,MIT
 getpass,0.1.6,MIT
 gettext_i18n_rails,1.8.0,MIT
 gettext_i18n_rails_js,1.2.0,MIT
-gitaly,0.6.0,MIT
+gitaly,0.8.0,MIT
 github-linguist,4.7.6,MIT
 github-markup,1.4.0,MIT
 gitlab-flowdock-git-hook,1.0.1,MIT
@@ -467,7 +472,6 @@ has-flag,1.0.0,MIT
 has-unicode,2.0.1,ISC
 hash-sum,1.0.2,MIT
 hash.js,1.0.3,MIT
-hasha,2.2.0,MIT
 hashie,3.5.5,MIT
 hashie-forbidden_attributes,0.1.1,MIT
 hawk,3.1.3,New BSD
@@ -487,7 +491,7 @@ htmlparser2,3.9.2,MIT
 http,0.9.8,MIT
 http-cookie,1.0.3,MIT
 http-deceiver,1.2.7,MIT
-http-errors,1.5.1,MIT
+http-errors,1.6.1,MIT
 http-form_data,1.0.1,MIT
 http-proxy,1.16.2,MIT
 http-proxy-middleware,0.17.4,MIT
@@ -516,7 +520,7 @@ inquirer,0.12.0,MIT
 interpret,1.0.1,MIT
 invariant,2.2.2,New BSD
 invert-kv,1.0.0,MIT
-ipaddr.js,1.2.0,MIT
+ipaddr.js,1.3.0,MIT
 ipaddress,0.8.3,MIT
 is-absolute,0.2.6,MIT
 is-absolute-url,2.1.0,MIT
@@ -563,7 +567,7 @@ istanbul-lib-instrument,1.4.2,New BSD
 istanbul-lib-report,1.0.0-alpha.3,New BSD
 istanbul-lib-source-maps,1.1.0,New BSD
 istanbul-reports,1.0.1,New BSD
-jasmine-core,2.5.2,MIT
+jasmine-core,2.6.3,MIT
 jasmine-jquery,2.1.1,MIT
 jed,1.1.1,MIT
 jira-ruby,1.1.2,MIT
@@ -587,7 +591,6 @@ json-stable-stringify,1.0.1,MIT
 json-stringify-safe,5.0.1,ISC
 json3,3.3.2,MIT
 json5,0.5.1,MIT
-jsonfile,2.4.0,MIT
 jsonify,0.0.0,Public Domain
 jsonpointer,4.0.1,MIT
 jsprim,1.3.1,MIT
@@ -595,17 +598,14 @@ jszip,3.1.3,(MIT OR GPL-3.0)
 jszip-utils,0.0.2,MIT or GPLv3
 jwt,1.5.6,MIT
 kaminari,0.17.0,MIT
-karma,1.4.1,MIT
+karma,1.7.0,MIT
 karma-coverage-istanbul-reporter,0.2.0,MIT
 karma-jasmine,1.1.0,MIT
 karma-mocha-reporter,2.2.2,MIT
-karma-phantomjs-launcher,1.0.2,MIT
 karma-sourcemap-loader,0.3.7,MIT
 karma-webpack,2.0.2,MIT
-kew,0.7.0,Apache 2.0
 kgio,2.10.0,LGPL-2.1+
 kind-of,3.1.0,MIT
-klaw,1.3.1,MIT
 kubeclient,2.2.0,MIT
 latest-version,1.0.1,MIT
 launchy,2.4.3,ISC
@@ -667,7 +667,7 @@ methods,1.1.2,MIT
 micromatch,2.3.11,MIT
 miller-rabin,4.0.0,MIT
 mime,1.3.4,MIT
-mime-db,1.26.0,MIT
+mime-db,1.27.0,MIT
 mime-types,2.99.3,"MIT,Artistic-2.0,GPL-2.0"
 mimemagic,0.3.0,MIT
 mini_portile2,2.1.0,MIT
@@ -675,16 +675,20 @@ minimalistic-assert,1.0.0,ISC
 minimatch,3.0.3,ISC
 minimist,0.0.8,MIT
 mkdirp,0.5.1,MIT
+mmap2,2.2.6,ruby
 moment,2.17.1,MIT
 mousetrap,1.4.6,Apache 2.0
 mousetrap-rails,1.4.6,"MIT,Apache"
 ms,0.7.2,MIT
+msgpack,1.1.0,Apache 2.0
 multi_json,1.12.1,MIT
 multi_xml,0.6.0,MIT
 multipart-post,2.0.0,MIT
 mustermann,0.4.0,MIT
 mustermann-grape,0.4.0,MIT
 mute-stream,0.0.5,ISC
+mysql2,0.3.20,MIT
+name-all-modules-plugin,1.0.1,MIT
 nan,2.5.1,MIT
 natural-compare,1.4.0,MIT
 negotiator,0.6.1,MIT
@@ -774,9 +778,16 @@ path-type,1.1.0,MIT
 pause-stream,0.0.11,"MIT,Apache2"
 pbkdf2,3.0.9,MIT
 pdfjs-dist,1.8.252,Apache 2.0
-pend,1.2.0,MIT
+peek,1.0.1,MIT
+peek-gc,0.0.2,MIT
+peek-host,1.0.0,MIT
+peek-mysql2,1.1.0,MIT
+peek-performance_bar,1.2.1,MIT
+peek-pg,1.3.0,MIT
+peek-rblineprof,0.2.0,MIT
+peek-redis,1.2.0,MIT
+peek-sidekiq,1.0.3,MIT
 pg,0.18.4,"BSD,ruby,GPL"
-phantomjs-prebuilt,2.1.14,Apache 2.0
 pify,2.3.0,MIT
 pikaday,1.5.1,"BSD,MIT"
 pinkie,2.0.4,MIT
@@ -833,8 +844,9 @@ private,0.1.7,MIT
 process,0.11.9,MIT
 process-nextick-args,1.0.7,MIT
 progress,1.1.8,MIT
+prometheus-client-mmap,0.7.0.beta5,Apache 2.0
 proto-list,1.2.4,ISC
-proxy-addr,1.1.3,MIT
+proxy-addr,1.1.4,MIT
 prr,0.0.0,MIT
 ps-tree,1.1.0,MIT
 pseudomap,1.0.2,ISC
@@ -843,7 +855,7 @@ punycode,1.4.1,MIT
 pyu-ruby-sasl,0.0.3.3,MIT
 q,1.5.0,MIT
 qjobs,1.1.5,MIT
-qs,6.2.0,New BSD
+qs,6.3.0,New BSD
 query-string,4.3.2,MIT
 querystring,0.2.0,MIT
 querystring-es3,0.2.1,MIT
@@ -868,7 +880,7 @@ randomatic,1.1.6,MIT
 randombytes,2.0.3,MIT
 range-parser,1.2.0,MIT
 raphael,2.2.7,MIT
-raven-js,3.15.0,Simplified BSD
+raven-js,3.14.0,Simplified BSD
 raw-body,2.2.0,MIT
 raw-loader,0.5.1,MIT
 rc,1.1.6,(BSD-2-Clause OR MIT OR Apache-2.0)
@@ -906,7 +918,6 @@ repeat-element,1.1.2,MIT
 repeat-string,1.6.1,MIT
 repeating,2.0.1,MIT
 request,2.79.0,Apache 2.0
-request-progress,2.0.1,MIT
 request_store,1.3.1,MIT
 require-directory,2.1.1,MIT
 require-from-string,1.2.1,MIT
@@ -924,7 +935,7 @@ rimraf,2.5.4,ISC
 rinku,2.0.0,ISC
 ripemd160,1.0.1,New BSD
 rotp,2.1.2,MIT
-rouge,2.0.7,MIT
+rouge,2.1.0,MIT
 rqrcode,0.7.0,MIT
 rqrcode-rails3,0.1.7,MIT
 ruby-fogbugz,0.2.1,MIT
@@ -933,7 +944,7 @@ ruby-saml,1.4.1,MIT
 ruby_parser,3.8.4,MIT
 rubyntlm,0.5.2,MIT
 rubypants,0.2.0,BSD
-rufus-scheduler,3.1.10,MIT
+rufus-scheduler,3.4.0,MIT
 rugged,0.25.1.1,MIT
 run-async,0.1.0,MIT
 rx-lite,3.1.2,Apache 2.0
@@ -952,20 +963,20 @@ select2,3.5.2-browserify,unknown
 select2-rails,3.5.9.3,MIT
 semver,5.3.0,ISC
 semver-diff,2.1.0,MIT
-send,0.14.2,MIT
+send,0.15.3,MIT
 sentry-raven,2.4.0,Apache 2.0
 serve-index,1.8.0,MIT
-serve-static,1.11.2,MIT
+serve-static,1.12.3,MIT
 set-blocking,2.0.0,ISC
 set-immediate-shim,1.0.1,MIT
 setimmediate,1.0.5,MIT
-setprototypeof,1.0.2,ISC
+setprototypeof,1.0.3,ISC
 settingslogic,2.0.9,MIT
 sexp_processor,4.8.0,MIT
 sha.js,2.4.8,MIT
 shelljs,0.7.6,New BSD
 sidekiq,5.0.0,LGPL
-sidekiq-cron,0.4.4,MIT
+sidekiq-cron,0.6.0,MIT
 sidekiq-limit_fetch,3.4.0,MIT
 sigmund,1.0.1,ISC
 signal-exit,3.0.2,ISC
@@ -975,9 +986,9 @@ slash,1.0.0,MIT
 slice-ansi,0.0.4,MIT
 slide,1.1.6,ISC
 sntp,1.0.9,BSD
-socket.io,1.7.2,MIT
+socket.io,1.7.3,MIT
 socket.io-adapter,0.5.0,MIT
-socket.io-client,1.7.2,MIT
+socket.io-client,1.7.3,MIT
 socket.io-parser,2.3.1,MIT
 sockjs,0.3.18,MIT
 sockjs-client,1.0.1,MIT
@@ -1030,7 +1041,6 @@ thread_safe,0.3.6,Apache 2.0
 three,0.84.0,MIT
 three-orbit-controls,82.1.0,MIT
 three-stl-loader,1.0.4,MIT
-throttleit,1.0.0,MIT
 through,2.3.8,MIT
 tilt,2.0.6,MIT
 timeago.js,2.0.5,MIT
@@ -1038,7 +1048,7 @@ timed-out,2.0.0,MIT
 timers-browserify,2.0.2,MIT
 timfel-krb5-auth,0.8.3,LGPL
 tiny-emitter,1.1.0,MIT
-tmp,0.0.28,MIT
+tmp,0.0.31,MIT
 to-array,0.1.4,MIT
 to-arraybuffer,1.0.1,MIT
 to-fast-properties,1.0.2,MIT
@@ -1054,15 +1064,15 @@ tty-browserify,0.0.0,MIT
 tunnel-agent,0.4.3,Apache 2.0
 tweetnacl,0.14.5,Unlicense
 type-check,0.3.2,MIT
-type-is,1.6.14,MIT
+type-is,1.6.15,MIT
 typedarray,0.0.6,MIT
 tzinfo,1.2.2,MIT
 u2f,0.2.1,MIT
 uglifier,2.7.2,MIT
-uglify-js,2.8.21,Simplified BSD
+uglify-js,2.8.27,Simplified BSD
 uglify-to-browserify,1.0.2,MIT
 uid-number,0.0.6,ISC
-ultron,1.0.2,MIT
+ultron,1.1.0,MIT
 unc-path-regex,0.1.2,MIT
 undefsafe,0.0.3,MIT / http://rem.mit-license.org
 underscore,1.8.3,MIT
@@ -1081,14 +1091,14 @@ url-loader,0.5.8,MIT
 url-parse,1.0.5,MIT
 url_safe_base64,0.2.2,MIT
 user-home,2.0.0,MIT
-useragent,2.1.12,MIT
+useragent,2.1.13,MIT
 util,0.10.3,MIT
 util-deprecate,1.0.2,MIT
 utils-merge,1.0.0,MIT
 uuid,3.0.1,MIT
 validate-npm-package-license,3.0.1,Apache 2.0
 validates_hostname,1.0.6,MIT
-vary,1.1.0,MIT
+vary,1.1.1,MIT
 vendors,1.0.1,MIT
 verror,1.3.6,MIT
 version_sorter,2.1.0,MIT
@@ -1107,8 +1117,8 @@ vue-template-es2015-compiler,1.5.1,MIT
 warden,1.2.6,MIT
 watchpack,1.3.1,MIT
 wbuf,1.7.2,MIT
-webpack,2.3.3,MIT
-webpack-bundle-analyzer,2.3.0,MIT
+webpack,2.6.1,MIT
+webpack-bundle-analyzer,2.8.2,MIT
 webpack-dev-middleware,1.10.0,MIT
 webpack-dev-server,2.4.2,MIT
 webpack-rails,0.9.10,MIT
@@ -1127,14 +1137,14 @@ wrap-ansi,2.1.0,MIT
 wrappy,1.0.2,ISC
 write,0.2.1,MIT
 write-file-atomic,1.3.1,ISC
-ws,1.1.1,MIT
+ws,2.3.1,MIT
 wtf-8,1.0.0,MIT
 xdg-basedir,2.0.0,MIT
+xml-simple,1.1.5,ruby
 xmlhttprequest-ssl,1.5.3,MIT
 xtend,4.0.1,MIT
 y18n,3.2.1,ISC
 yallist,2.1.2,ISC
 yargs,3.10.0,MIT
 yargs-parser,4.2.1,ISC
-yauzl,2.4.1,MIT
 yeast,0.1.2,MIT
diff --git a/yarn.lock b/yarn.lock
index b902d5235d012498dd51d3a9f6fa21d0e34ce207..b04eebe60af2420d4f287b89d8f35c78fe36e474 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -265,6 +265,15 @@ babel-core@^6.22.1, babel-core@^6.23.0:
     slash "^1.0.0"
     source-map "^0.5.0"
 
+babel-eslint@^7.2.1:
+  version "7.2.1"
+  resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-7.2.1.tgz#079422eb73ba811e3ca0865ce87af29327f8c52f"
+  dependencies:
+    babel-code-frame "^6.22.0"
+    babel-traverse "^6.23.1"
+    babel-types "^6.23.0"
+    babylon "^6.16.1"
+
 babel-generator@^6.18.0, babel-generator@^6.23.0:
   version "6.23.0"
   resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.23.0.tgz#6b8edab956ef3116f79d8c84c5a3c05f32a74bc5"
@@ -816,10 +825,14 @@ babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.22.0, babel-types@^6.23
     lodash "^4.2.0"
     to-fast-properties "^1.0.1"
 
-babylon@^6.11.0, babylon@^6.13.0, babylon@^6.15.0:
+babylon@^6.11.0:
   version "6.15.0"
   resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.15.0.tgz#ba65cfa1a80e1759b0e89fb562e27dccae70348e"
 
+babylon@^6.13.0, babylon@^6.15.0, babylon@^6.16.1:
+  version "6.16.1"
+  resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.16.1.tgz#30c5a22f481978a9e7f8cdfdf496b11d94b404d3"
+
 backo2@1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947"